|
| 1 | +# Part of OpenSPP. See LICENSE file for full copyright and licensing details. |
| 2 | + |
| 3 | +import logging |
| 4 | + |
| 5 | +from odoo import _, api, fields, models |
| 6 | + |
| 7 | +_logger = logging.getLogger(__name__) |
| 8 | + |
| 9 | + |
| 10 | +class SppProgram(models.Model): |
| 11 | + """ |
| 12 | + Extends spp.program to add hazard/emergency response capabilities. |
| 13 | +
|
| 14 | + This extension links programs to hazard incidents, enabling: |
| 15 | + - Emergency program eligibility based on hazard impacts |
| 16 | + - Targeting of affected populations |
| 17 | + - Emergency mode with relaxed compliance rules |
| 18 | + """ |
| 19 | + |
| 20 | + _inherit = "spp.program" |
| 21 | + |
| 22 | + target_incident_ids = fields.Many2many( |
| 23 | + "spp.hazard.incident", |
| 24 | + "spp_program_hazard_incident_rel", |
| 25 | + "program_id", |
| 26 | + "incident_id", |
| 27 | + string="Target Incidents", |
| 28 | + help="Incidents this program responds to. Registrants with verified " |
| 29 | + "impacts from these incidents may be eligible for this program.", |
| 30 | + ) |
| 31 | + is_emergency_program = fields.Boolean( |
| 32 | + compute="_compute_is_emergency_program", |
| 33 | + store=True, |
| 34 | + help="Whether this program is responding to any active emergency incidents", |
| 35 | + ) |
| 36 | + qualifying_damage_levels = fields.Selection( |
| 37 | + [ |
| 38 | + ("any", "Any Damage Level"), |
| 39 | + ("moderate_up", "Moderate and Above"), |
| 40 | + ("severe_up", "Severe and Above"), |
| 41 | + ("critical_only", "Critical/Totally Damaged Only"), |
| 42 | + ], |
| 43 | + default="any", |
| 44 | + help="Minimum damage level required for emergency eligibility", |
| 45 | + ) |
| 46 | + is_emergency_mode = fields.Boolean( |
| 47 | + string="Emergency Mode", |
| 48 | + default=False, |
| 49 | + help="When enabled, relaxed compliance rules apply for this program. " |
| 50 | + "Automatically enabled when linked to active incidents.", |
| 51 | + ) |
| 52 | + target_incident_count = fields.Integer( |
| 53 | + compute="_compute_target_incident_count", |
| 54 | + string="Number of Target Incidents", |
| 55 | + store=True, |
| 56 | + ) |
| 57 | + affected_registrant_count = fields.Integer( |
| 58 | + compute="_compute_affected_registrant_count", |
| 59 | + string="Potentially Affected Registrants", |
| 60 | + ) |
| 61 | + |
| 62 | + @api.depends("target_incident_ids") |
| 63 | + def _compute_target_incident_count(self): |
| 64 | + """Compute the number of target incidents.""" |
| 65 | + for rec in self: |
| 66 | + rec.target_incident_count = len(rec.target_incident_ids) |
| 67 | + |
| 68 | + @api.depends("target_incident_ids.status") |
| 69 | + def _compute_is_emergency_program(self): |
| 70 | + """Compute whether this is an emergency program based on linked incidents.""" |
| 71 | + for rec in self: |
| 72 | + rec.is_emergency_program = bool( |
| 73 | + rec.target_incident_ids.filtered(lambda i: i.status in ("alert", "active", "recovery")) |
| 74 | + ) |
| 75 | + |
| 76 | + @api.depends("target_incident_ids", "qualifying_damage_levels") |
| 77 | + def _compute_affected_registrant_count(self): |
| 78 | + """Compute the number of potentially affected registrants.""" |
| 79 | + for rec in self: |
| 80 | + if not rec.target_incident_ids: |
| 81 | + rec.affected_registrant_count = 0 |
| 82 | + continue |
| 83 | + |
| 84 | + # Build domain for qualifying damage levels |
| 85 | + damage_domain = rec._get_damage_level_domain() |
| 86 | + |
| 87 | + # Count unique registrants with qualifying impacts |
| 88 | + impacts = self.env["spp.hazard.impact"].search( |
| 89 | + [ |
| 90 | + ("incident_id", "in", rec.target_incident_ids.ids), |
| 91 | + ("verification_status", "=", "verified"), |
| 92 | + ] |
| 93 | + + damage_domain |
| 94 | + ) |
| 95 | + rec.affected_registrant_count = len(impacts.mapped("registrant_id")) |
| 96 | + |
| 97 | + def _get_damage_level_domain(self): |
| 98 | + """Get the domain filter for qualifying damage levels.""" |
| 99 | + self.ensure_one() |
| 100 | + if self.qualifying_damage_levels == "any": |
| 101 | + return [] |
| 102 | + elif self.qualifying_damage_levels == "moderate_up": |
| 103 | + return [("damage_level", "in", ("moderate", "severe", "critical", "partially_damaged", "totally_damaged"))] |
| 104 | + elif self.qualifying_damage_levels == "severe_up": |
| 105 | + return [("damage_level", "in", ("severe", "critical", "totally_damaged"))] |
| 106 | + elif self.qualifying_damage_levels == "critical_only": |
| 107 | + return [("damage_level", "in", ("critical", "totally_damaged"))] |
| 108 | + return [] |
| 109 | + |
| 110 | + def get_emergency_eligible_registrants(self): |
| 111 | + """ |
| 112 | + Get registrants eligible for this emergency program based on hazard impacts. |
| 113 | +
|
| 114 | + Returns registrants who: |
| 115 | + - Have verified impact from one of the target incidents |
| 116 | + - Meet the qualifying damage level threshold |
| 117 | +
|
| 118 | + :return: recordset of res.partner |
| 119 | + """ |
| 120 | + self.ensure_one() |
| 121 | + if not self.target_incident_ids: |
| 122 | + return self.env["res.partner"].browse() |
| 123 | + |
| 124 | + damage_domain = self._get_damage_level_domain() |
| 125 | + |
| 126 | + # Find qualifying impacts |
| 127 | + impacts = self.env["spp.hazard.impact"].search( |
| 128 | + [ |
| 129 | + ("incident_id", "in", self.target_incident_ids.ids), |
| 130 | + ("verification_status", "=", "verified"), |
| 131 | + ] |
| 132 | + + damage_domain |
| 133 | + ) |
| 134 | + |
| 135 | + return impacts.mapped("registrant_id") |
| 136 | + |
| 137 | + def action_view_target_incidents(self): |
| 138 | + """Open a list view of target incidents.""" |
| 139 | + self.ensure_one() |
| 140 | + return { |
| 141 | + "name": _("Target Incidents - %s", self.name), |
| 142 | + "type": "ir.actions.act_window", |
| 143 | + "res_model": "spp.hazard.incident", |
| 144 | + "view_mode": "list,form", |
| 145 | + "domain": [("id", "in", self.target_incident_ids.ids)], |
| 146 | + "context": {}, |
| 147 | + } |
| 148 | + |
| 149 | + def action_view_affected_registrants(self): |
| 150 | + """Open a list view of potentially affected registrants.""" |
| 151 | + self.ensure_one() |
| 152 | + registrants = self.get_emergency_eligible_registrants() |
| 153 | + return { |
| 154 | + "name": _("Affected Registrants - %s", self.name), |
| 155 | + "type": "ir.actions.act_window", |
| 156 | + "res_model": "res.partner", |
| 157 | + "view_mode": "list,form", |
| 158 | + "domain": [("id", "in", registrants.ids)], |
| 159 | + "context": {}, |
| 160 | + } |
0 commit comments