diff --git a/spp_hazard/README.rst b/spp_hazard/README.rst index 5f12ee79..e50a257c 100644 --- a/spp_hazard/README.rst +++ b/spp_hazard/README.rst @@ -22,10 +22,12 @@ OpenSPP Hazard & Emergency Management |badge1| |badge2| |badge3| -Records disaster events and tracks their impact on individual -registrants. Supports hierarchical hazard classification, geographic -scope tracking, severity levels, and verification workflows to enable -targeted emergency response and humanitarian assistance. +Records disaster events and tracks their impact on registrants. Provides +hierarchical hazard classification, geographic scope tracking via areas +and GIS geofences, severity levels, and verification workflows to enable +targeted emergency response and humanitarian assistance. Incident and +impact records include full chatter integration for audit trails and +activity scheduling. Key Capabilities ~~~~~~~~~~~~~~~~ @@ -36,6 +38,8 @@ Key Capabilities status (alert, active, recovery, closed) - Link incidents to geographic areas with area-specific severity overrides +- Define hazard zone geofences linked to specific incidents via + ``spp.gis.geofence`` extension - Track registrant-level impacts by type (physical, economic, health, social) and damage level - Verify impact records with workflow states (reported, verified, @@ -47,48 +51,53 @@ Key Capabilities Key Models ~~~~~~~~~~ -+------------------------------+---------------------------------------+ -| Model | Description | -+==============================+=======================================+ -| ``spp.hazard.category`` | Hierarchical classification of hazard | -| | types | -+------------------------------+---------------------------------------+ -| ``spp.hazard.incident`` | Specific disaster event with dates, | -| | severity, and affected areas | -+------------------------------+---------------------------------------+ -| ``spp.hazard.incident.area`` | Links incident to area with | -| | area-specific details | -+------------------------------+---------------------------------------+ -| ``spp.hazard.impact`` | Records impact on a registrant (type, | -| | damage level, verification) | -+------------------------------+---------------------------------------+ -| ``spp.hazard.impact.type`` | Classification of impact types by | -| | category | -+------------------------------+---------------------------------------+ -| ``res.partner`` (extended) | Adds hazard impact tracking fields to | -| | registrants | -+------------------------------+---------------------------------------+ ++---------------------------------+-----------------------------------+ +| Model | Description | ++=================================+===================================+ +| ``spp.hazard.category`` | Hierarchical classification of | +| | hazard types | ++---------------------------------+-----------------------------------+ +| ``spp.hazard.incident`` | Specific disaster event with | +| | dates, severity, and affected | +| | areas | ++---------------------------------+-----------------------------------+ +| ``spp.hazard.incident.area`` | Links incident to area with | +| | area-specific severity override | ++---------------------------------+-----------------------------------+ +| ``spp.hazard.impact`` | Records impact on a registrant | +| | (type, damage level, | +| | verification) | ++---------------------------------+-----------------------------------+ +| ``spp.hazard.impact.type`` | Classification of impact types by | +| | category | ++---------------------------------+-----------------------------------+ +| ``res.partner`` (extended) | Adds hazard impact tracking | +| | fields to registrants | ++---------------------------------+-----------------------------------+ +| ``spp.gis.geofence`` (extended) | Adds ``hazard_zone`` geofence | +| | type and incident linking | ++---------------------------------+-----------------------------------+ Configuration ~~~~~~~~~~~~~ After installing: -1. Navigate to **Hazard & Emergency > Configuration > Hazard +1. Navigate to **Hazard and Emergency > Configuration > Hazard Categories** 2. Create or review hierarchical hazard categories (e.g., Natural Disasters, Man-made Disasters) -3. Navigate to **Hazard & Emergency > Configuration > Impact Types** +3. Navigate to **Hazard and Emergency > Configuration > Impact Types** 4. Review pre-configured impact types (Displacement, Property Damage, Injury, etc.) or create custom types UI Location ~~~~~~~~~~~ -- **Menu**: Hazard & Emergency (top-level application menu) -- **Incidents**: Hazard & Emergency > Incidents > All Incidents -- **Impacts**: Hazard & Emergency > Incidents > Impact Records -- **Configuration**: Hazard & Emergency > Configuration (accessible to +- **Menu**: Hazard and Emergency (top-level application menu) +- **Incidents**: Hazard and Emergency > Incidents > All Incidents +- **Impacts**: Hazard and Emergency > Incidents > Impact Records +- **Configuration**: Hazard and Emergency > Configuration (accessible to managers only) - **Registrant Form**: Stat button shows impact count; "Emergency Response" tab displays impact records list @@ -96,20 +105,20 @@ UI Location Security ~~~~~~~~ -+----------------------------------+-----------------------------------+ -| Group | Access | -+==================================+===================================+ -| ``group_hazard_viewer`` | Read-only access to all hazard | -| | records | -+----------------------------------+-----------------------------------+ -| ``group_hazard_officer`` | Create and manage incidents and | -| | impacts (no delete) | -+----------------------------------+-----------------------------------+ -| ``group_hazard_manager`` | Full CRUD access including | -| | configuration | -+----------------------------------+-----------------------------------+ -| ``spp_security.group_spp_admin`` | Inherits manager access | -+----------------------------------+-----------------------------------+ ++----------------------------------+----------------------------------+ +| Group | Access | ++==================================+==================================+ +| ``group_hazard_viewer`` | Read-only access to all hazard | +| | records | ++----------------------------------+----------------------------------+ +| ``group_hazard_officer`` | Read/write/create incidents and | +| | impacts (no delete) | ++----------------------------------+----------------------------------+ +| ``group_hazard_manager`` | Full CRUD access including | +| | configuration models | ++----------------------------------+----------------------------------+ +| ``spp_security.group_spp_admin`` | Inherits manager access | ++----------------------------------+----------------------------------+ Extension Points ~~~~~~~~~~~~~~~~ @@ -121,17 +130,1079 @@ Extension Points (e.g., crop damage for farmer registries) - Override ``bulk_create_impacts()`` to customize mass impact record creation +- Extend ``spp.gis.geofence`` to add behavior for ``hazard_zone`` + geofence type Dependencies ~~~~~~~~~~~~ -``base``, ``spp_security``, ``spp_registry``, ``spp_area`` +``base``, ``spp_security``, ``spp_registry``, ``spp_area``, ``spp_gis`` **Table of contents** .. contents:: :local: +Usage +===== + +Testing guide for QA validation of the OpenSPP Hazard and Emergency +Management module. + +**Prerequisites** + +1. OpenSPP instance running with ``spp_hazard`` installed (and demo data + loaded) +2. Three test users configured with different security groups: + + - **Admin user** — system administrator (has all access) + - **Manager user** — assigned to Hazard: Manager group + - **Officer user** — assigned to Hazard: Officer group + - **Viewer user** — assigned to Hazard: Viewer group + +3. At least one area record in ``spp.area`` (created via Registry > + Areas) +4. At least one registrant (individual) with an area assigned + +To assign hazard groups: **Settings > Users & Companies > Users > +(select user) > Access Rights tab > Hazard section** — choose Viewer, +Officer, or Manager. + +-------------- + +**1. Module Installation** + ++------+------------------------------+------------------------------+ +| Step | Action | Expected Result | ++======+==============================+==============================+ +| 1.1 | Navigate to **Apps**, search | "OpenSPP Hazard & Emergency | +| | for "Hazard" | Management" appears | ++------+------------------------------+------------------------------+ +| 1.2 | Install the module | Installation completes | +| | | without errors | ++------+------------------------------+------------------------------+ +| 1.3 | Refresh the browser | "Hazard and Emergency" | +| | | appears in the top | +| | | application menu bar | ++------+------------------------------+------------------------------+ + +-------------- + +**2. Menu Structure** + +Log in as **Admin** or **Manager**. + ++------+------------------------------+------------------------------+ +| Step | Action | Expected Result | ++======+==============================+==============================+ +| 2.1 | Click **Hazard and | Submenu appears with | +| | Emergency** in the top menu | "Incidents" and | +| | | "Configuration" sections | ++------+------------------------------+------------------------------+ +| 2.2 | Click **Incidents > All | Incident list view loads | +| | Incidents** | (URL ends with | +| | | ``/hazard-incidents``) | ++------+------------------------------+------------------------------+ +| 2.3 | Click **Incidents > Impact | Impact list view loads (URL | +| | Records** | ends with | +| | | ``/hazard-impacts``) | ++------+------------------------------+------------------------------+ +| 2.4 | Click **Configuration > | Category list loads with | +| | Hazard Categories** | pre-seeded Active filter | +| | | (URL: | +| | | ``/hazard-categories``) | ++------+------------------------------+------------------------------+ +| 2.5 | Click **Configuration > | Impact type list loads with | +| | Impact Types** | Active filter (URL: | +| | | ``/hazard-impact-types``) | ++------+------------------------------+------------------------------+ + +-------------- + +**3. Hazard Categories (Configuration)** + +**3.1 Pre-installed Data (Demo)** + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 3.1.1 | Open **Configuration > | List shows categories. If | +| | Hazard Categories** | demo data loaded: "Natural | +| | | Disaster", "Storm", | +| | | "Typhoon", "Geological", | +| | | "Earthquake", "Volcanic | +| | | Eruption", "Hydrological", | +| | | "Flood", "Climate", | +| | | "Drought", "Health | +| | | Emergency", "Pandemic", | +| | | "Disease Outbreak", | +| | | "Conflict", "Armed | +| | | Conflict", "Economic | +| | | Shock", "Food Crisis" | ++-------+-----------------------------+-----------------------------+ +| 3.1.2 | Check the "Complete Name" | Hierarchical names shown, | +| | column | e.g., "Natural Disaster > | +| | | Storm > Typhoon" | ++-------+-----------------------------+-----------------------------+ +| 3.1.3 | Check the "Incidents" | Shows integer count for | +| | column | each category | ++-------+-----------------------------+-----------------------------+ + +**3.2 Create a Category** + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 3.2.1 | Click **New** | Form opens with Name field | +| | | focused | ++-------+-----------------------------+-----------------------------+ +| 3.2.2 | Enter Name: | Fields accept input | +| | ``Test Wildfire``, Code: | | +| | ``TEST_WILDFIRE`` | | ++-------+-----------------------------+-----------------------------+ +| 3.2.3 | Set Parent Category to | Dropdown shows only active | +| | "Natural Disaster" | categories | ++-------+-----------------------------+-----------------------------+ +| 3.2.4 | Save | Record saves. Complete Name | +| | | shows "Natural Disaster > | +| | | Test Wildfire" | ++-------+-----------------------------+-----------------------------+ +| 3.2.5 | Navigate to the parent | "Subcategories" section is | +| | "Natural Disaster" record | visible below the form and | +| | | lists "Test Wildfire" | ++-------+-----------------------------+-----------------------------+ + +**3.3 Category Incident Count** + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 3.3.1 | Open a category that has | "Number of Incidents" field | +| | linked incidents (e.g., | shows the count in the | +| | "Typhoon" if demo data | right column | +| | loaded) | | ++-------+-----------------------------+-----------------------------+ +| 3.3.2 | Verify the count matches | Count should be accurate | +| | the actual number of | | +| | incidents | | ++-------+-----------------------------+-----------------------------+ + +**3.4 Category Code Uniqueness** + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 3.4.1 | Create a new category with | Error: "A hazard category | +| | Code = ``TEST_WILDFIRE`` | with this code already | +| | (same as 3.2) | exists!" | ++-------+-----------------------------+-----------------------------+ + +**3.5 Deactivation** + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 3.5.1 | Open a category, uncheck | Red "Inactive" ribbon | +| | "Active", save | appears at top-right of | +| | | form | ++-------+-----------------------------+-----------------------------+ +| 3.5.2 | Return to the list (Active | Inactive category is hidden | +| | filter is on by default) | from the list | ++-------+-----------------------------+-----------------------------+ +| 3.5.3 | In search bar, click | Inactive category now | +| | "Active" filter to remove | visible | +| | it, add "Inactive" filter | | ++-------+-----------------------------+-----------------------------+ + +**3.6 Search and Grouping** + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 3.6.1 | Type in search bar, select | Filters by name | +| | "Name" search | | ++-------+-----------------------------+-----------------------------+ +| 3.6.2 | Type in search bar, select | Filters by code | +| | "Code" search | | ++-------+-----------------------------+-----------------------------+ +| 3.6.3 | Use Filters > Active / | Filters correctly | +| | Inactive | | ++-------+-----------------------------+-----------------------------+ +| 3.6.4 | Use Group By > Parent | Categories grouped by | +| | | parent | ++-------+-----------------------------+-----------------------------+ + +-------------- + +**4. Impact Types (Configuration)** + +**4.1 Pre-installed Data** + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 4.1.1 | Open **Configuration > | List shows 14 | +| | Impact Types** | pre-configured types with | +| | | drag handles for reordering | ++-------+-----------------------------+-----------------------------+ +| 4.1.2 | Verify Physical types | Displacement, Property | +| | | Damage, Injury, | +| | | Death/Fatality | ++-------+-----------------------------+-----------------------------+ +| 4.1.3 | Verify Economic types | Livelihood Loss, Asset | +| | | Destruction, Crop Loss, | +| | | Livestock Loss | ++-------+-----------------------------+-----------------------------+ +| 4.1.4 | Verify Health types | Illness, Disability, | +| | | Psychological Impact | ++-------+-----------------------------+-----------------------------+ +| 4.1.5 | Verify Social types | Family Separation, | +| | | Community Disruption, | +| | | Education Disruption | ++-------+-----------------------------+-----------------------------+ +| 4.1.6 | Check "Category" column | Shows as badge widgets | +| | | (Physical, Economic, | +| | | Health, Social) | ++-------+-----------------------------+-----------------------------+ +| 4.1.7 | Check "Usage" column | Shows 0 for types not yet | +| | | linked to any impact record | ++-------+-----------------------------+-----------------------------+ + +**4.2 Create an Impact Type** + ++-------+-----------------------------+---------------------------+ +| Step | Action | Expected Result | ++=======+=============================+===========================+ +| 4.2.1 | Click **New** | Form opens | ++-------+-----------------------------+---------------------------+ +| 4.2.2 | Enter Name: | Fields accept input | +| | ``Water Contamination``, | | +| | Code: ``WATER_CONTAM``, | | +| | Category: Health | | ++-------+-----------------------------+---------------------------+ +| 4.2.3 | Save | Record saves successfully | ++-------+-----------------------------+---------------------------+ + +**4.3 Reorder via Drag** + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 4.3.1 | In the list view, grab the | Row becomes draggable | +| | drag handle (hamburger | | +| | icon) on a type | | ++-------+-----------------------------+-----------------------------+ +| 4.3.2 | Drop it at a different | Sequence updates; reload | +| | position | confirms new order | ++-------+-----------------------------+-----------------------------+ + +**4.4 Code Uniqueness** + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 4.4.1 | Create another type with | Error: "An impact type with | +| | Code = ``WATER_CONTAM`` | this code already exists!" | ++-------+-----------------------------+-----------------------------+ + +**4.5 Search Filters** + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 4.5.1 | Use Filters > Physical / | Filters by category | +| | Economic / Health / Social | correctly | ++-------+-----------------------------+-----------------------------+ +| 4.5.2 | Use Group By > Category | Types grouped into 4 | +| | | category sections | ++-------+-----------------------------+-----------------------------+ + +-------------- + +**5. Hazard Incidents** + +**5.1 Demo Data** + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 5.1.1 | Open **Incidents > All | If demo loaded: 3 incidents | +| | Incidents** | visible | ++-------+-----------------------------+-----------------------------+ +| 5.1.2 | "Demo Typhoon Alpha" | Status: Recovery (yellow | +| | | badge), Severity: Level 4 | ++-------+-----------------------------+-----------------------------+ +| 5.1.3 | "Demo Flooding Event" | Status: Closed (grey | +| | | badge), has End Date | ++-------+-----------------------------+-----------------------------+ +| 5.1.4 | "Demo Drought 2024" | Status: Active (green | +| | | badge), no End Date | ++-------+-----------------------------+-----------------------------+ + +**5.2 Create an Incident** + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 5.2.1 | Click **New** | Form opens. Status defaults | +| | | to "Active". Statusbar | +| | | shows: Alert / Active / | +| | | Recovery / Closed | ++-------+-----------------------------+-----------------------------+ +| 5.2.2 | Enter Name: | Fields accept input | +| | ``Test Earthquake``, Code: | | +| | ``TEST-EQ-001`` | | ++-------+-----------------------------+-----------------------------+ +| 5.2.3 | Select Category: search for | Dropdown shows only active | +| | "Earthquake" | categories, filtered by | +| | | search text | ++-------+-----------------------------+-----------------------------+ +| 5.2.4 | Set Start Date: today's | Date picker works | +| | date | | ++-------+-----------------------------+-----------------------------+ +| 5.2.5 | Leave End Date empty | Allowed (ongoing incident) | ++-------+-----------------------------+-----------------------------+ +| 5.2.6 | Set Severity: "Level 3 - | Selection dropdown works | +| | Significant" | | ++-------+-----------------------------+-----------------------------+ +| 5.2.7 | Save | Record saves. "Ongoing" | +| | | ribbon appears (yellow, | +| | | top-right corner) | ++-------+-----------------------------+-----------------------------+ + +**5.3 Incident Form Layout** + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 5.3.1 | Check the header | "Start Recovery" button | +| | | visible (since status is | +| | | Active). "Close Incident" | +| | | button visible. Status bar | +| | | shows current step | ++-------+-----------------------------+-----------------------------+ +| 5.3.2 | Check the button box | "Affected" stat button | +| | (top-right) | (fa-users icon) always | +| | | visible. "Areas" stat | +| | | button visible only if | +| | | areas > 0 | ++-------+-----------------------------+-----------------------------+ +| 5.3.3 | Check left column | Code, Category, Severity | +| | | fields | ++-------+-----------------------------+-----------------------------+ +| 5.3.4 | Check right column | Start Date, End Date fields | ++-------+-----------------------------+-----------------------------+ +| 5.3.5 | Check notebook tabs | Three tabs: "Description", | +| | | "Affected Areas", "Impacts" | ++-------+-----------------------------+-----------------------------+ +| 5.3.6 | Click "Description" tab | HTML editor for incident | +| | | description | ++-------+-----------------------------+-----------------------------+ +| 5.3.7 | Click "Affected Areas" tab | Instruction text visible. | +| | | Inline-editable list with: | +| | | Area, Severity Override, | +| | | Affected Population | +| | | Estimate, Notes | ++-------+-----------------------------+-----------------------------+ +| 5.3.8 | Click "Impacts" tab | List showing Registrant, | +| | | Impact Type, Damage Level, | +| | | Impact Date, Verification | +| | | Status | ++-------+-----------------------------+-----------------------------+ +| 5.3.9 | Scroll to bottom of form | Chatter (message log + | +| | | activity scheduling) | +| | | visible below the form | ++-------+-----------------------------+-----------------------------+ + +**5.4 Code Uniqueness** + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 5.4.1 | Create another incident | Error: "An incident with | +| | with Code = ``TEST-EQ-001`` | this code already exists!" | ++-------+-----------------------------+-----------------------------+ + +**5.5 Date Validation** + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 5.5.1 | Edit the incident, set End | Error: "End date must be | +| | Date before Start Date | after start date." | ++-------+-----------------------------+-----------------------------+ +| 5.5.2 | Set End Date after Start | Saves successfully. | +| | Date, save | "Ongoing" ribbon disappears | +| | | (end date is now set) | ++-------+-----------------------------+-----------------------------+ +| 5.5.3 | Clear End Date, save | "Ongoing" ribbon reappears | ++-------+-----------------------------+-----------------------------+ + +**5.6 Status Workflow (Lifecycle)** + +Use the incident created in 5.2 (starts as "Active"). + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 5.6.1 | Click **Start Recovery** | Status changes to | +| | | "Recovery". Button changes | +| | | to "Set Active". "Close | +| | | Incident" still visible | ++-------+-----------------------------+-----------------------------+ +| 5.6.2 | Click **Set Active** | Status returns to "Active". | +| | | "Start Recovery" button | +| | | returns | ++-------+-----------------------------+-----------------------------+ +| 5.6.3 | Click **Start Recovery** | Status = Recovery | +| | again | | ++-------+-----------------------------+-----------------------------+ +| 5.6.4 | Click **Close Incident** | Status = Closed. End Date | +| | | auto-filled with today if | +| | | it was empty. All fields | +| | | become readonly | ++-------+-----------------------------+-----------------------------+ +| 5.6.5 | Check the form in Closed | No action buttons visible | +| | state | (Set Active, Start | +| | | Recovery, Close are all | +| | | hidden). All fields are | +| | | readonly. Description, | +| | | areas, and impacts are | +| | | readonly | ++-------+-----------------------------+-----------------------------+ + +**5.7 Add Affected Areas** + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 5.7.1 | Open a non-closed incident, | Instruction text: "Add | +| | go to "Affected Areas" tab | areas affected by this | +| | | incident..." | ++-------+-----------------------------+-----------------------------+ +| 5.7.2 | Click "Add a line" | New row appears. Area field | +| | | is a dropdown (no create | +| | | option) | ++-------+-----------------------------+-----------------------------+ +| 5.7.3 | Select an Area | Area appears in the row | ++-------+-----------------------------+-----------------------------+ +| 5.7.4 | Set Severity Override: | Selection dropdown works | +| | "Level 5 - Catastrophic" | | ++-------+-----------------------------+-----------------------------+ +| 5.7.5 | Enter Affected Population | Integer field accepts value | +| | Estimate: 5000 | | ++-------+-----------------------------+-----------------------------+ +| 5.7.6 | Save | Area link saved. "Areas" | +| | | stat button now visible | +| | | with count = 1 | ++-------+-----------------------------+-----------------------------+ +| 5.7.7 | Try adding the same area | Error: "This area is | +| | again | already linked to this | +| | | incident!" | ++-------+-----------------------------+-----------------------------+ + +**5.8 Stat Buttons** + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 5.8.1 | Click "Affected" stat | Opens Impact Records list | +| | button | filtered to this incident | ++-------+-----------------------------+-----------------------------+ +| 5.8.2 | Click browser back | Returns to incident form | ++-------+-----------------------------+-----------------------------+ +| 5.8.3 | Click "Areas" stat button | Opens Area list filtered to | +| | | linked areas | ++-------+-----------------------------+-----------------------------+ + +**5.9 Search and Filters (Incident List)** + ++--------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++========+=============================+=============================+ +| 5.9.1 | Use search: type a name | Filters by incident name | ++--------+-----------------------------+-----------------------------+ +| 5.9.2 | Use search: select Code | Filters by code | +| | field | | ++--------+-----------------------------+-----------------------------+ +| 5.9.3 | Use search: select Category | Filters by category | ++--------+-----------------------------+-----------------------------+ +| 5.9.4 | Filters > Alert | Shows only alert-status | +| | | incidents (blue-tinted | +| | | rows) | ++--------+-----------------------------+-----------------------------+ +| 5.9.5 | Filters > Active | Shows only active-status | +| | | incidents | ++--------+-----------------------------+-----------------------------+ +| 5.9.6 | Filters > Recovery | Shows only recovery-status | +| | | incidents (yellow-tinted | +| | | rows) | ++--------+-----------------------------+-----------------------------+ +| 5.9.7 | Filters > Closed | Shows only closed incidents | +| | | (grey/muted rows) | ++--------+-----------------------------+-----------------------------+ +| 5.9.8 | Filters > Ongoing | Shows incidents with | +| | | ``is_ongoing = True`` | ++--------+-----------------------------+-----------------------------+ +| 5.9.9 | Group By > Status | Groups into status sections | ++--------+-----------------------------+-----------------------------+ +| 5.9.10 | Group By > Category | Groups by hazard category | ++--------+-----------------------------+-----------------------------+ +| 5.9.11 | Group By > Severity | Groups by severity level | ++--------+-----------------------------+-----------------------------+ +| 5.9.12 | Group By > Start Date | Groups by month | ++--------+-----------------------------+-----------------------------+ + +**5.10 List View Decorations** + ++--------+----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++========+============================+=============================+ +| 5.10.1 | Check row coloring | Alert rows: blue tint. | +| | | Recovery rows: yellow tint. | +| | | Closed rows: grey/muted. | +| | | Active rows: default (no | +| | | special coloring) | ++--------+----------------------------+-----------------------------+ +| 5.10.2 | Check Status column badges | Alert: blue badge. Active: | +| | | green badge. Recovery: | +| | | yellow badge. Closed: grey | +| | | badge | ++--------+----------------------------+-----------------------------+ +| 5.10.3 | Check columns visible | Name, Code, Category, Start | +| | | Date, End Date (optional), | +| | | Status, Severity, Areas, | +| | | Affected | ++--------+----------------------------+-----------------------------+ + +-------------- + +**6. Impact Records** + +**6.1 Create an Impact Record** + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 6.1.1 | Navigate to **Incidents > | Form opens. Verification | +| | Impact Records**, click | Status defaults to | +| | **New** | "Reported" | ++-------+-----------------------------+-----------------------------+ +| 6.1.2 | Select Registrant (h1 title | Dropdown shows registrants | +| | area) | only | +| | | (``is_registrant = True``). | +| | | "Create" option not | +| | | available | ++-------+-----------------------------+-----------------------------+ +| 6.1.3 | Select Incident (h2 below | Dropdown shows incidents. | +| | registrant) | "Create" option not | +| | | available | ++-------+-----------------------------+-----------------------------+ +| 6.1.4 | Set Impact Type: | Only active impact types | +| | "Displacement" | shown. "Create" option not | +| | | available | ++-------+-----------------------------+-----------------------------+ +| 6.1.5 | Set Damage Level: "Severe" | Selection works (Minimal, | +| | | Moderate, Severe, Critical, | +| | | Partially Damaged, Totally | +| | | Damaged) | ++-------+-----------------------------+-----------------------------+ +| 6.1.6 | Set Impact Date: same as or | Date accepted | +| | after incident start date | | ++-------+-----------------------------+-----------------------------+ +| 6.1.7 | Save | Record saves. Statusbar | +| | | shows: Reported / Verified | +| | | / Disputed / Closed | ++-------+-----------------------------+-----------------------------+ + +**6.2 Impact Date Validation** + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 6.2.1 | Edit the impact, set Impact | Error: "Impact date cannot | +| | Date before the incident's | be before the incident | +| | Start Date | start date." | ++-------+-----------------------------+-----------------------------+ + +**6.3 Duplicate Impact Constraint** + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 6.3.1 | Create another impact with | Error: "This registrant | +| | the same Incident + | already has an impact of | +| | Registrant + Impact Type | this type for this | +| | | incident!" | ++-------+-----------------------------+-----------------------------+ +| 6.3.2 | Same Incident + Registrant | Creates successfully — | +| | but different Impact Type | different types allowed for | +| | (e.g., "Property Damage") | same registrant | ++-------+-----------------------------+-----------------------------+ + +**6.4 Verification Workflow** + +Using the impact created in 6.1 (starts as "Reported"): + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 6.4.1 | Check header buttons | "Verify" (primary), "Mark | +| | | Disputed" (secondary), | +| | | "Close" (secondary) | +| | | visible. "Reset to | +| | | Reported" hidden | ++-------+-----------------------------+-----------------------------+ +| 6.4.2 | Check "Verification | Hidden (only shows when not | +| | Information" section | in "Reported" status) | ++-------+-----------------------------+-----------------------------+ +| 6.4.3 | Click **Verify** | Status → Verified. | +| | | "Verification Information" | +| | | section appears showing | +| | | "Verified By" (current | +| | | user) and "Verification | +| | | Date" (now). "Verify" | +| | | button disappears. "Mark | +| | | Disputed" and "Close" | +| | | remain | ++-------+-----------------------------+-----------------------------+ +| 6.4.4 | Click **Mark Disputed** | Status → Disputed. "Reset | +| | | to Reported" button | +| | | appears. "Verify" hidden. | +| | | "Close" visible | ++-------+-----------------------------+-----------------------------+ +| 6.4.5 | Click **Reset to Reported** | Status → Reported. Verified | +| | | By and Verification Date | +| | | cleared. Back to initial | +| | | button state | ++-------+-----------------------------+-----------------------------+ +| 6.4.6 | Click **Close** | Status → Closed. All fields | +| | | become readonly. Only | +| | | "Reset to Reported" visible | ++-------+-----------------------------+-----------------------------+ +| 6.4.7 | Click **Reset to Reported** | Status → Reported again. | +| | | Fields editable again | ++-------+-----------------------------+-----------------------------+ + +**6.5 Impact Form Layout** + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 6.5.1 | Check title area | Registrant name displayed | +| | | as h1, Incident name as h2 | ++-------+-----------------------------+-----------------------------+ +| 6.5.2 | Check left group | Shows Incident Name | +| | ("incident_info") | (readonly) and Impact | +| | | Category (readonly) for | +| | | quick context | ++-------+-----------------------------+-----------------------------+ +| 6.5.3 | Check right group | Impact Type, Damage Level, | +| | ("impact_details") | Impact Date fields | ++-------+-----------------------------+-----------------------------+ +| 6.5.4 | Check "Notes" section | Notes field displayed below | +| | | verification info with | +| | | full-width layout | ++-------+-----------------------------+-----------------------------+ +| 6.5.5 | Check bottom of form | Chatter visible (message | +| | | log and activities) | ++-------+-----------------------------+-----------------------------+ + +**6.6 Search and Filters (Impact List)** + ++--------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++========+=============================+=============================+ +| 6.6.1 | Search by Incident | Filters to selected | +| | | incident | ++--------+-----------------------------+-----------------------------+ +| 6.6.2 | Search by Registrant | Filters to selected | +| | | registrant | ++--------+-----------------------------+-----------------------------+ +| 6.6.3 | Search by Impact Type | Filters to selected type | ++--------+-----------------------------+-----------------------------+ +| 6.6.4 | Filters > Reported / | Each shows correct subset | +| | Verified / Disputed / | | +| | Closed | | ++--------+-----------------------------+-----------------------------+ +| 6.6.5 | Filters > Critical | Shows only ``critical`` and | +| | | ``totally_damaged`` damage | +| | | levels | ++--------+-----------------------------+-----------------------------+ +| 6.6.6 | Filters > Severe or | Shows ``severe``, | +| | Critical | ``critical``, and | +| | | ``totally_damaged`` | ++--------+-----------------------------+-----------------------------+ +| 6.6.7 | Group By > Incident | Groups by incident | ++--------+-----------------------------+-----------------------------+ +| 6.6.8 | Group By > Impact Type | Groups by type | ++--------+-----------------------------+-----------------------------+ +| 6.6.9 | Group By > Damage Level | Groups by severity | ++--------+-----------------------------+-----------------------------+ +| 6.6.10 | Group By > Verification | Groups by status | +| | Status | | ++--------+-----------------------------+-----------------------------+ +| 6.6.11 | Group By > Impact Date | Groups by month | ++--------+-----------------------------+-----------------------------+ + +**6.7 Impact List Decorations** + ++-------+----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+============================+=============================+ +| 6.7.1 | Check row coloring | Reported: blue. Verified: | +| | | green. Disputed: yellow. | +| | | Closed: grey/muted | ++-------+----------------------------+-----------------------------+ +| 6.7.2 | Verification Status badges | Same color coding as rows | ++-------+----------------------------+-----------------------------+ +| 6.7.3 | Damage Level column | Shows as badge widget | +| | | (neutral color) | ++-------+----------------------------+-----------------------------+ +| 6.7.4 | Optional columns | "Verified By" and "Verified | +| | | Date" available under | +| | | column options (hidden by | +| | | default) | ++-------+----------------------------+-----------------------------+ + +-------------- + +**7. Registrant Integration** + +**7.1 Stat Button** + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 7.1.1 | Open a registrant that has | No "Impacts" stat button | +| | NO impact records | visible in button box | ++-------+-----------------------------+-----------------------------+ +| 7.1.2 | Open a registrant that HAS | "Impacts" stat button with | +| | impact records | bolt icon visible, showing | +| | | count | ++-------+-----------------------------+-----------------------------+ +| 7.1.3 | Click the stat button | Opens Impact Records list | +| | | filtered to this registrant | ++-------+-----------------------------+-----------------------------+ + +**7.2 Emergency Response Tab** + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 7.2.1 | Open a registrant with NO | "Emergency Response" tab | +| | impacts | visible. Tab content shows: | +| | | "No emergency response | +| | | records yet. Impact records | +| | | will appear here when this | +| | | registrant is affected by a | +| | | hazard incident." | ++-------+-----------------------------+-----------------------------+ +| 7.2.2 | Open a registrant with | "Emergency Response" tab | +| | impacts from a CLOSED | shows the "Incident | +| | incident | Impacts" section with a | +| | | readonly list of impacts. | +| | | No yellow alert banner | ++-------+-----------------------------+-----------------------------+ +| 7.2.3 | Open a registrant with | Yellow alert banner with | +| | impacts from an ACTIVE or | warning icon: "Active | +| | ALERT incident | incident impacts recorded. | +| | | This registrant is affected | +| | | by one or more ongoing | +| | | hazard incidents." Impact | +| | | list shows below | ++-------+-----------------------------+-----------------------------+ +| 7.2.4 | Check the impact list | Incident, Impact Type, | +| | columns | Damage Level (badge), | +| | | Impact Date, Verification | +| | | Status (badge with colors) | ++-------+-----------------------------+-----------------------------+ +| 7.2.5 | Verify the list is readonly | Cannot edit, add, or delete | +| | | impact records from this | +| | | view | ++-------+-----------------------------+-----------------------------+ + +**7.3 Registrant List Extensions** + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 7.3.1 | Open the registrants list | "Impacts" and "Active | +| | view | Impact" columns available | +| | | in column options (hidden | +| | | by default) | ++-------+-----------------------------+-----------------------------+ +| 7.3.2 | Enable both columns via | Columns show correct counts | +| | column selector | and boolean values | ++-------+-----------------------------+-----------------------------+ + +**7.4 Registrant Search Extensions** + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 7.4.1 | In registrant search, check | "Has Active Impact" and | +| | Filters | "Has Any Impact" filters | +| | | available | ++-------+-----------------------------+-----------------------------+ +| 7.4.2 | Apply "Has Active Impact" | Shows only registrants with | +| | | impacts from | +| | | active/alert/recovery | +| | | incidents | ++-------+-----------------------------+-----------------------------+ +| 7.4.3 | Apply "Has Any Impact" | Shows all registrants with | +| | | any impact count > 0 | ++-------+-----------------------------+-----------------------------+ + +-------------- + +**8. Security and Access Control** + +**8.1 Viewer Access** + +Log in as the **Viewer** user. + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 8.1.1 | Navigate to Hazard and | Menu visible. "Incidents" | +| | Emergency | submenu visible | ++-------+-----------------------------+-----------------------------+ +| 8.1.2 | Check for "Configuration" | NOT visible (restricted to | +| | submenu | managers) | ++-------+-----------------------------+-----------------------------+ +| 8.1.3 | Open Incidents > All | Can view the list and open | +| | Incidents | records | ++-------+-----------------------------+-----------------------------+ +| 8.1.4 | Try to create a new | Access denied or "New" | +| | incident (click New) | button not available | ++-------+-----------------------------+-----------------------------+ +| 8.1.5 | Try to edit an existing | Cannot save changes (write | +| | incident | access denied) | ++-------+-----------------------------+-----------------------------+ +| 8.1.6 | Open Incidents > Impact | Can view but not create or | +| | Records | edit | ++-------+-----------------------------+-----------------------------+ + +**8.2 Officer Access** + +Log in as the **Officer** user. + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 8.2.1 | Check for "Configuration" | NOT visible (restricted to | +| | submenu | managers) | ++-------+-----------------------------+-----------------------------+ +| 8.2.2 | Create a new incident | Succeeds | ++-------+-----------------------------+-----------------------------+ +| 8.2.3 | Edit an existing incident | Succeeds | ++-------+-----------------------------+-----------------------------+ +| 8.2.4 | Try to delete an incident | Access denied (officer has | +| | | no delete permission) | ++-------+-----------------------------+-----------------------------+ +| 8.2.5 | Create a new impact record | Succeeds | ++-------+-----------------------------+-----------------------------+ +| 8.2.6 | Edit an existing impact | Succeeds | +| | record | | ++-------+-----------------------------+-----------------------------+ +| 8.2.7 | Try to delete an impact | Access denied | +| | record | | ++-------+-----------------------------+-----------------------------+ +| 8.2.8 | Open a registrant's | Can view impact records | +| | Emergency Response tab | (read access) | ++-------+-----------------------------+-----------------------------+ + +**8.3 Manager Access** + +Log in as the **Manager** user. + ++-------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++=======+=============================+=============================+ +| 8.3.1 | Check for "Configuration" | Visible with "Hazard | +| | submenu | Categories" and "Impact | +| | | Types" | ++-------+-----------------------------+-----------------------------+ +| 8.3.2 | Create a hazard category | Succeeds | ++-------+-----------------------------+-----------------------------+ +| 8.3.3 | Edit a hazard category | Succeeds | ++-------+-----------------------------+-----------------------------+ +| 8.3.4 | Delete a hazard category | Succeeds | +| | (with no linked incidents) | | ++-------+-----------------------------+-----------------------------+ +| 8.3.5 | Try to delete a category | Blocked by | +| | linked to an incident | ``ondelete="restrict"`` — | +| | | error shown | ++-------+-----------------------------+-----------------------------+ +| 8.3.6 | Create, edit, delete impact | All succeed | +| | types | | ++-------+-----------------------------+-----------------------------+ +| 8.3.7 | Create, edit, delete | All succeed | +| | incidents | | ++-------+-----------------------------+-----------------------------+ +| 8.3.8 | Create, edit, delete impact | All succeed | +| | records | | ++-------+-----------------------------+-----------------------------+ + +-------------- + +**9. URL Paths (Pretty URLs)** + +==== ============================= ===================================== +Step Action Expected Result +==== ============================= ===================================== +9.1 Navigate to All Incidents URL contains ``/hazard-incidents`` +9.2 Navigate to Impact Records URL contains ``/hazard-impacts`` +9.3 Navigate to Hazard Categories URL contains ``/hazard-categories`` +9.4 Navigate to Impact Types URL contains ``/hazard-impact-types`` +==== ============================= ===================================== + +-------------- + +**10. Empty States** + ++------+------------------------------+------------------------------+ +| Step | Action | Expected Result | ++======+==============================+==============================+ +| 10.1 | Open Incidents list with no | Smiling face icon with text: | +| | records (new database, no | "Record your first hazard | +| | demo) | incident" and helper text | +| | | about tracking events | ++------+------------------------------+------------------------------+ +| 10.2 | Open Impact Records list | Smiling face icon with text: | +| | with no records | "Record impacts on | +| | | registrants" | ++------+------------------------------+------------------------------+ +| 10.3 | Open Categories list with no | Smiling face icon with text: | +| | records | "Create your first hazard | +| | | category" | ++------+------------------------------+------------------------------+ +| 10.4 | Open Impact Types list with | Smiling face icon with text: | +| | no records | "Define impact types" | ++------+------------------------------+------------------------------+ + +-------------- + +**11. Chatter and Tracking** + +**11.1 Incident Tracking** + ++--------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++========+=============================+=============================+ +| 11.1.1 | Create an incident, then | Chatter log shows "Status: | +| | change its status | Active → Recovery" (or | +| | | whichever transition) | ++--------+-----------------------------+-----------------------------+ +| 11.1.2 | Change the severity | Chatter log shows | +| | | "Severity: Level 2 → Level | +| | | 4" | ++--------+-----------------------------+-----------------------------+ +| 11.1.3 | Change the category | Chatter log shows category | +| | | change | ++--------+-----------------------------+-----------------------------+ +| 11.1.4 | Post a message in the | Message appears in log | +| | chatter | | ++--------+-----------------------------+-----------------------------+ +| 11.1.5 | Schedule an activity | Activity appears in the | +| | | scheduled activities | +| | | section | ++--------+-----------------------------+-----------------------------+ + +**11.2 Impact Tracking** + ++--------+-----------------------------+-----------------------------+ +| Step | Action | Expected Result | ++========+=============================+=============================+ +| 11.2.1 | Create an impact, then | Chatter shows "Verification | +| | verify it | Status: Reported → | +| | | Verified", "Verified By" | +| | | set, "Verification Date" | +| | | set | ++--------+-----------------------------+-----------------------------+ +| 11.2.2 | Change the damage level | Chatter log shows change | ++--------+-----------------------------+-----------------------------+ +| 11.2.3 | Post a message | Message appears | ++--------+-----------------------------+-----------------------------+ + +-------------- + +**12. Edge Cases** + ++------+------------------------------+------------------------------+ +| Step | Action | Expected Result | ++======+==============================+==============================+ +| 12.1 | Create an incident with | "Set Active" button visible. | +| | status "Alert" (create, then | "Close Incident" visible. | +| | note: default is Active — | "Start Recovery" NOT visible | +| | the "alert" status must be | (only visible for Active) | +| | set by creating with status | | +| | alert or changing the | | +| | initial state) | | ++------+------------------------------+------------------------------+ +| 12.2 | Create an incident, add | All tabs (Description, | +| | areas, then close it | Affected Areas, Impacts) | +| | | become readonly. Cannot add | +| | | or remove areas | ++------+------------------------------+------------------------------+ +| 12.3 | Close an incident that has | End Date is auto-populated | +| | no end date | with today's date | ++------+------------------------------+------------------------------+ +| 12.4 | Close an incident that | End Date preserved (not | +| | already has an end date | overwritten) | ++------+------------------------------+------------------------------+ +| 12.5 | Create an impact, set the | Accepted (boundary case: | +| | date to exactly the incident | equal dates are valid) | +| | start date | | ++------+------------------------------+------------------------------+ +| 12.6 | Delete an incident that has | Incident deletes. Geofence's | +| | geofences linked to it | "Related Incident" field | +| | | becomes empty (set null) | ++------+------------------------------+------------------------------+ +| 12.7 | Try to delete an area that | Blocked by | +| | is linked to an incident via | ``ondelete="restrict"`` | +| | incident_area_ids | | ++------+------------------------------+------------------------------+ + +-------------- + +**13. Cross-Module: Groups/Households** + ++------+------------------------------+------------------------------+ +| Step | Action | Expected Result | ++======+==============================+==============================+ +| 13.1 | Open a group (household) | "Emergency Response" tab | +| | registrant form | visible (groups reuse the | +| | | individual form view) | ++------+------------------------------+------------------------------+ +| 13.2 | Create an impact record for | Impact record creation | +| | a group registrant | succeeds; registrant domain | +| | | allows groups | +| | | (``is_registrant=True`` | +| | | includes both) | ++------+------------------------------+------------------------------+ +| 13.3 | View the group's Emergency | Impact records appear | +| | Response tab | correctly | ++------+------------------------------+------------------------------+ + +-------------- + +**Defect Reference** + +All previously known issues from the fix plan +(``docs/plans/SPP_HAZARD_FIXES_PLAN.md``) have been resolved. If you +encounter unexpected behavior, please report it as a new issue. + ++-----+------------------------------+------------------------------+ +| ID | Known Limitation | Notes | ++=====+==============================+==============================+ +| N/A | Badge decorations (severity, | Odoo's badge widget does not | +| | damage level) rely on color | support embedded icons. | +| | only | Screen reader users rely on | +| | | the text label | ++-----+------------------------------+------------------------------+ + Bug Tracker =========== diff --git a/spp_hazard/__manifest__.py b/spp_hazard/__manifest__.py index 30da58df..99b35064 100644 --- a/spp_hazard/__manifest__.py +++ b/spp_hazard/__manifest__.py @@ -23,6 +23,7 @@ "spp_gis", ], "data": [ + "security/privileges.xml", "security/groups.xml", "security/ir.model.access.csv", "data/impact_type_data.xml", diff --git a/spp_hazard/demo/hazard_demo.xml b/spp_hazard/demo/hazard_demo.xml index 318f4c89..4f8b0633 100644 --- a/spp_hazard/demo/hazard_demo.xml +++ b/spp_hazard/demo/hazard_demo.xml @@ -195,4 +195,145 @@

Below-normal rainfall for 6+ months.

]]> + + + + Northern Province + DEMO-AREA-NORTH + + + + Coastal District + DEMO-AREA-COAST + + + + Inland Valley + DEMO-AREA-INLAND + + + + + + + 5 + 12500 + Direct hit from typhoon eye; severe structural damage reported. + + + + + + 4 + 8200 + Storm surge and coastal flooding; fishing communities heavily impacted. + + + + + + 3 + 3400 + Riverine flooding submerged low-lying settlements for 14 days. + + + + + + 3 + 6700 + Agricultural zone with total crop failure reported in two consecutive seasons. + + + + + SANTOS, MARIA + True + False + + + + + REYES, JOSE + True + False + + + + + CRUZ, ANA + True + False + + + + + + + + + + severe + 2024-11-15 + verified + Family evacuated to barangay hall; home partially submerged. + + + + + + + totally_damaged + 2024-11-16 + verified + Roof collapsed; walls structurally compromised. + + + + + + + moderate + 2024-11-18 + reported + Fishing boat damaged; unable to work for several weeks. + + + + + + + + partially_damaged + 2024-10-03 + closed + Ground floor flooded; damage assessed and assistance provided. + + + + + + + + critical + 2024-05-01 + verified + Total loss of rice crop due to lack of irrigation water. + diff --git a/spp_hazard/models/hazard_category.py b/spp_hazard/models/hazard_category.py index fb5b0e50..7629560d 100644 --- a/spp_hazard/models/hazard_category.py +++ b/spp_hazard/models/hazard_category.py @@ -1,11 +1,7 @@ # Part of OpenSPP. See LICENSE file for full copyright and licensing details. -import logging - from odoo import api, fields, models -_logger = logging.getLogger(__name__) - class HazardCategory(models.Model): """ @@ -83,8 +79,14 @@ def _compute_complete_name(self): @api.depends("incident_ids") def _compute_incident_count(self): """Compute the number of incidents linked to this category.""" + data = self.env["spp.hazard.incident"].read_group( + [("category_id", "in", self.ids)], + ["category_id"], + ["category_id"], + ) + mapped = {d["category_id"][0]: d["category_id_count"] for d in data} for rec in self: - rec.incident_count = len(rec.incident_ids) + rec.incident_count = mapped.get(rec.id, 0) def action_view_incidents(self): """Open a list view of incidents for this category.""" diff --git a/spp_hazard/models/hazard_impact.py b/spp_hazard/models/hazard_impact.py index 804b4e97..5340aae7 100644 --- a/spp_hazard/models/hazard_impact.py +++ b/spp_hazard/models/hazard_impact.py @@ -7,6 +7,8 @@ _logger = logging.getLogger(__name__) +BATCH_SIZE = 500 + class HazardImpact(models.Model): """ @@ -125,8 +127,6 @@ class HazardImpact(models.Model): @api.constrains("impact_date", "incident_id") def _check_impact_date(self): """Validate that impact_date is not before the incident start_date.""" - # Prefetch incidents to avoid N+1 queries - self.mapped("incident_id") for rec in self: if rec.impact_date and rec.incident_id.start_date: if rec.impact_date < rec.incident_id.start_date: @@ -222,5 +222,15 @@ def bulk_create_impacts(self, incident, area, impact_type, damage_level): ) if vals_list: - return self.create(vals_list) + created = self.browse() + for i in range(0, len(vals_list), BATCH_SIZE): + batch = vals_list[i : i + BATCH_SIZE] + created |= self.create(batch) + _logger.info( + "Created %d impact records for incident '%s' in area '%s'", + len(created), + incident.name, + area.name, + ) + return created return self.browse() diff --git a/spp_hazard/models/hazard_impact_type.py b/spp_hazard/models/hazard_impact_type.py index b07d186e..030a057e 100644 --- a/spp_hazard/models/hazard_impact_type.py +++ b/spp_hazard/models/hazard_impact_type.py @@ -1,11 +1,7 @@ # Part of OpenSPP. See LICENSE file for full copyright and licensing details. -import logging - from odoo import api, fields, models -_logger = logging.getLogger(__name__) - class HazardImpactType(models.Model): """ @@ -68,5 +64,11 @@ class HazardImpactType(models.Model): @api.depends("impact_ids") def _compute_impact_count(self): """Compute the number of impact records using this type.""" + data = self.env["spp.hazard.impact"].read_group( + [("impact_type_id", "in", self.ids)], + ["impact_type_id"], + ["impact_type_id"], + ) + mapped = {d["impact_type_id"][0]: d["impact_type_id_count"] for d in data} for rec in self: - rec.impact_count = len(rec.impact_ids) + rec.impact_count = mapped.get(rec.id, 0) diff --git a/spp_hazard/models/hazard_incident.py b/spp_hazard/models/hazard_incident.py index 5c4b878a..93dd776a 100644 --- a/spp_hazard/models/hazard_incident.py +++ b/spp_hazard/models/hazard_incident.py @@ -141,23 +141,44 @@ def _compute_is_ongoing(self): "recovery", ) - @api.depends("area_ids") + @api.depends("area_ids", "incident_area_ids.area_id") def _compute_area_count(self): - """Compute the number of affected areas.""" + """Compute the number of affected areas from both M2M and detail records.""" for rec in self: - rec.area_count = len(rec.area_ids) + detail_areas = rec.incident_area_ids.mapped("area_id") + all_areas = rec.area_ids | detail_areas + rec.area_count = len(all_areas) @api.depends("impact_ids") def _compute_impact_count(self): """Compute the number of impact records.""" + data = self.env["spp.hazard.impact"].read_group( + [("incident_id", "in", self.ids)], + ["incident_id"], + ["incident_id"], + ) + mapped = {d["incident_id"][0]: d["incident_id_count"] for d in data} for rec in self: - rec.impact_count = len(rec.impact_ids) + rec.impact_count = mapped.get(rec.id, 0) @api.depends("impact_ids.registrant_id") def _compute_affected_registrant_count(self): """Compute the number of unique affected registrants.""" + if not self.ids: + self.affected_registrant_count = 0 + return + self.env.cr.execute( + """ + SELECT incident_id, COUNT(DISTINCT registrant_id) + FROM spp_hazard_impact + WHERE incident_id IN %s + GROUP BY incident_id + """, + [tuple(self.ids)], + ) + mapped = dict(self.env.cr.fetchall()) for rec in self: - rec.affected_registrant_count = len(rec.impact_ids.mapped("registrant_id")) + rec.affected_registrant_count = mapped.get(rec.id, 0) def action_set_active(self): """Set incident status to active.""" @@ -169,11 +190,17 @@ def action_set_recovery(self): def action_close(self): """Close the incident.""" - self.write( - { - "status": "closed", - "end_date": self.end_date or fields.Date.today(), - } + for rec in self: + rec.write( + { + "status": "closed", + "end_date": rec.end_date or fields.Date.today(), + } + ) + _logger.info( + "Closed %d incident(s): %s", + len(self), + ", ".join(self.mapped("name")), ) def action_view_impacts(self): @@ -188,6 +215,12 @@ def action_view_impacts(self): "context": {"default_incident_id": self.id}, } + def _get_all_area_ids(self): + """Get all affected area IDs from both M2M and detail records.""" + self.ensure_one() + detail_areas = self.incident_area_ids.mapped("area_id") + return (self.area_ids | detail_areas).ids + def action_view_areas(self): """Open a list view of affected areas.""" self.ensure_one() @@ -196,7 +229,7 @@ def action_view_areas(self): "type": "ir.actions.act_window", "res_model": "spp.area", "view_mode": "list,form", - "domain": [("id", "in", self.area_ids.ids)], + "domain": [("id", "in", self._get_all_area_ids())], } def identify_potentially_affected_registrants(self): @@ -207,14 +240,15 @@ def identify_potentially_affected_registrants(self): affected based on their location in the incident's geographic scope. """ self.ensure_one() - if not self.area_ids: + all_area_ids = self._get_all_area_ids() + if not all_area_ids: return self.env["res.partner"].browse() # Find registrants in affected areas return self.env["res.partner"].search( [ ("is_registrant", "=", True), - ("area_id", "in", self.area_ids.ids), + ("area_id", "in", all_area_ids), ] ) @@ -230,6 +264,7 @@ class HazardIncidentArea(models.Model): _name = "spp.hazard.incident.area" _description = "Hazard Incident Area" _order = "incident_id, area_id" + _rec_name = "display_name" incident_id = fields.Many2one( "spp.hazard.incident", @@ -267,13 +302,7 @@ class HazardIncidentArea(models.Model): "This area is already linked to this incident!", ) - def name_get(self): - """Return a descriptive name for the record.""" - # Prefetch related records to avoid N+1 queries - self.mapped("incident_id") - self.mapped("area_id") - result = [] + @api.depends("incident_id.name", "area_id.name") + def _compute_display_name(self): for rec in self: - name = f"{rec.incident_id.name} - {rec.area_id.name}" - result.append((rec.id, name)) - return result + rec.display_name = f"{rec.incident_id.name} - {rec.area_id.name}" diff --git a/spp_hazard/models/registrant.py b/spp_hazard/models/registrant.py index 49142ce0..63912038 100644 --- a/spp_hazard/models/registrant.py +++ b/spp_hazard/models/registrant.py @@ -1,11 +1,7 @@ # Part of OpenSPP. See LICENSE file for full copyright and licensing details. -import logging - from odoo import _, api, fields, models -_logger = logging.getLogger(__name__) - class ResPartner(models.Model): """ @@ -29,7 +25,6 @@ class ResPartner(models.Model): ) has_active_impact = fields.Boolean( compute="_compute_has_active_impact", - string="Has Active Impact", store=True, help="Whether the registrant has an impact from an active incident", ) @@ -37,15 +32,27 @@ class ResPartner(models.Model): @api.depends("hazard_impact_ids") def _compute_hazard_impact_count(self): """Compute the number of hazard impacts for this registrant.""" + data = self.env["spp.hazard.impact"].read_group( + [("registrant_id", "in", self.ids)], + ["registrant_id"], + ["registrant_id"], + ) + mapped = {d["registrant_id"][0]: d["registrant_id_count"] for d in data} for rec in self: - rec.hazard_impact_count = len(rec.hazard_impact_ids) + rec.hazard_impact_count = mapped.get(rec.id, 0) @api.depends("hazard_impact_ids", "hazard_impact_ids.incident_id.status") def _compute_has_active_impact(self): """Compute whether the registrant has an impact from an active incident.""" for rec in self: rec.has_active_impact = bool( - rec.hazard_impact_ids.filtered(lambda i: i.incident_id.status in ("alert", "active", "recovery")) + self.env["spp.hazard.impact"].search_count( + [ + ("registrant_id", "=", rec.id), + ("incident_id.status", "in", ("alert", "active", "recovery")), + ], + limit=1, + ) ) def action_view_hazard_impacts(self): diff --git a/spp_hazard/readme/DESCRIPTION.md b/spp_hazard/readme/DESCRIPTION.md index 4fb6fb34..bbb437d0 100644 --- a/spp_hazard/readme/DESCRIPTION.md +++ b/spp_hazard/readme/DESCRIPTION.md @@ -1,10 +1,14 @@ -Records disaster events and tracks their impact on individual registrants. Supports hierarchical hazard classification, geographic scope tracking, severity levels, and verification workflows to enable targeted emergency response and humanitarian assistance. +Records disaster events and tracks their impact on registrants. Provides hierarchical hazard +classification, geographic scope tracking via areas and GIS geofences, severity levels, and +verification workflows to enable targeted emergency response and humanitarian assistance. Incident +and impact records include full chatter integration for audit trails and activity scheduling. ### Key Capabilities - Define hazard categories in a tree structure (e.g., Natural > Storm > Typhoon) - Record incidents with start/end dates, severity levels, and lifecycle status (alert, active, recovery, closed) - Link incidents to geographic areas with area-specific severity overrides +- Define hazard zone geofences linked to specific incidents via `spp.gis.geofence` extension - Track registrant-level impacts by type (physical, economic, health, social) and damage level - Verify impact records with workflow states (reported, verified, disputed, closed) - Bulk-create impact records for all registrants in an affected area via `bulk_create_impacts()` @@ -12,47 +16,49 @@ Records disaster events and tracks their impact on individual registrants. Suppo ### Key Models -| Model | Description | -| -------------------------- | ---------------------------------------------------------------- | -| `spp.hazard.category` | Hierarchical classification of hazard types | -| `spp.hazard.incident` | Specific disaster event with dates, severity, and affected areas | -| `spp.hazard.incident.area` | Links incident to area with area-specific details | -| `spp.hazard.impact` | Records impact on a registrant (type, damage level, verification)| -| `spp.hazard.impact.type` | Classification of impact types by category | -| `res.partner` (extended) | Adds hazard impact tracking fields to registrants | +| Model | Description | +| ---------------------------- | ---------------------------------------------------------------- | +| `spp.hazard.category` | Hierarchical classification of hazard types | +| `spp.hazard.incident` | Specific disaster event with dates, severity, and affected areas | +| `spp.hazard.incident.area` | Links incident to area with area-specific severity override | +| `spp.hazard.impact` | Records impact on a registrant (type, damage level, verification)| +| `spp.hazard.impact.type` | Classification of impact types by category | +| `res.partner` (extended) | Adds hazard impact tracking fields to registrants | +| `spp.gis.geofence` (extended)| Adds `hazard_zone` geofence type and incident linking | ### Configuration After installing: -1. Navigate to **Hazard & Emergency > Configuration > Hazard Categories** +1. Navigate to **Hazard and Emergency > Configuration > Hazard Categories** 2. Create or review hierarchical hazard categories (e.g., Natural Disasters, Man-made Disasters) -3. Navigate to **Hazard & Emergency > Configuration > Impact Types** +3. Navigate to **Hazard and Emergency > Configuration > Impact Types** 4. Review pre-configured impact types (Displacement, Property Damage, Injury, etc.) or create custom types ### UI Location -- **Menu**: Hazard & Emergency (top-level application menu) -- **Incidents**: Hazard & Emergency > Incidents > All Incidents -- **Impacts**: Hazard & Emergency > Incidents > Impact Records -- **Configuration**: Hazard & Emergency > Configuration (accessible to managers only) +- **Menu**: Hazard and Emergency (top-level application menu) +- **Incidents**: Hazard and Emergency > Incidents > All Incidents +- **Impacts**: Hazard and Emergency > Incidents > Impact Records +- **Configuration**: Hazard and Emergency > Configuration (accessible to managers only) - **Registrant Form**: Stat button shows impact count; "Emergency Response" tab displays impact records list ### Security -| Group | Access | -| ------------------------------ | --------------------------------------------------- | -| `group_hazard_viewer` | Read-only access to all hazard records | -| `group_hazard_officer` | Create and manage incidents and impacts (no delete) | -| `group_hazard_manager` | Full CRUD access including configuration | -| `spp_security.group_spp_admin` | Inherits manager access | +| Group | Access | +| ------------------------------ | ----------------------------------------------------------------- | +| `group_hazard_viewer` | Read-only access to all hazard records | +| `group_hazard_officer` | Read/write/create incidents and impacts (no delete) | +| `group_hazard_manager` | Full CRUD access including configuration models | +| `spp_security.group_spp_admin` | Inherits manager access | ### Extension Points - Inherit `spp.hazard.incident` and override `identify_potentially_affected_registrants()` to customize targeting logic - Inherit `spp.hazard.impact` to add domain-specific impact fields (e.g., crop damage for farmer registries) - Override `bulk_create_impacts()` to customize mass impact record creation +- Extend `spp.gis.geofence` to add behavior for `hazard_zone` geofence type ### Dependencies -`base`, `spp_security`, `spp_registry`, `spp_area` +`base`, `spp_security`, `spp_registry`, `spp_area`, `spp_gis` diff --git a/spp_hazard/readme/USAGE.md b/spp_hazard/readme/USAGE.md new file mode 100644 index 00000000..e1118670 --- /dev/null +++ b/spp_hazard/readme/USAGE.md @@ -0,0 +1,486 @@ +Testing guide for QA validation of the OpenSPP Hazard and Emergency Management module. + +**Prerequisites** + +1. OpenSPP instance running with `spp_hazard` installed (and demo data loaded) +2. Three test users configured with different security groups: + - **Admin user** — system administrator (has all access) + - **Manager user** — assigned to Hazard: Manager group + - **Officer user** — assigned to Hazard: Officer group + - **Viewer user** — assigned to Hazard: Viewer group +3. At least one area record in `spp.area` (created via Registry > Areas) +4. At least one registrant (individual) with an area assigned + +To assign hazard groups: **Settings > Users & Companies > Users > (select user) > Access Rights +tab > Hazard section** — choose Viewer, Officer, or Manager. + +--- + +**1. Module Installation** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 1.1 | Navigate to **Apps**, search for "Hazard" | "OpenSPP Hazard & Emergency Management" appears | +| 1.2 | Install the module | Installation completes without errors | +| 1.3 | Refresh the browser | "Hazard and Emergency" appears in the top application menu bar | + +--- + +**2. Menu Structure** + +Log in as **Admin** or **Manager**. + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 2.1 | Click **Hazard and Emergency** in the top menu | Submenu appears with "Incidents" and "Configuration" sections | +| 2.2 | Click **Incidents > All Incidents** | Incident list view loads (URL ends with `/hazard-incidents`) | +| 2.3 | Click **Incidents > Impact Records** | Impact list view loads (URL ends with `/hazard-impacts`) | +| 2.4 | Click **Configuration > Hazard Categories** | Category list loads with pre-seeded Active filter (URL: `/hazard-categories`) | +| 2.5 | Click **Configuration > Impact Types** | Impact type list loads with Active filter (URL: `/hazard-impact-types`) | + +--- + +**3. Hazard Categories (Configuration)** + +**3.1 Pre-installed Data (Demo)** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 3.1.1 | Open **Configuration > Hazard Categories** | List shows categories. If demo data loaded: "Natural Disaster", "Storm", "Typhoon", "Geological", "Earthquake", "Volcanic Eruption", "Hydrological", "Flood", "Climate", "Drought", "Health Emergency", "Pandemic", "Disease Outbreak", "Conflict", "Armed Conflict", "Economic Shock", "Food Crisis" | +| 3.1.2 | Check the "Complete Name" column | Hierarchical names shown, e.g., "Natural Disaster > Storm > Typhoon" | +| 3.1.3 | Check the "Incidents" column | Shows integer count for each category | + +**3.2 Create a Category** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 3.2.1 | Click **New** | Form opens with Name field focused | +| 3.2.2 | Enter Name: `Test Wildfire`, Code: `TEST_WILDFIRE` | Fields accept input | +| 3.2.3 | Set Parent Category to "Natural Disaster" | Dropdown shows only active categories | +| 3.2.4 | Save | Record saves. Complete Name shows "Natural Disaster > Test Wildfire" | +| 3.2.5 | Navigate to the parent "Natural Disaster" record | "Subcategories" section is visible below the form and lists "Test Wildfire" | + +**3.3 Category Incident Count** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 3.3.1 | Open a category that has linked incidents (e.g., "Typhoon" if demo data loaded) | "Number of Incidents" field shows the count in the right column | +| 3.3.2 | Verify the count matches the actual number of incidents | Count should be accurate | + +**3.4 Category Code Uniqueness** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 3.4.1 | Create a new category with Code = `TEST_WILDFIRE` (same as 3.2) | Error: "A hazard category with this code already exists!" | + +**3.5 Deactivation** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 3.5.1 | Open a category, uncheck "Active", save | Red "Inactive" ribbon appears at top-right of form | +| 3.5.2 | Return to the list (Active filter is on by default) | Inactive category is hidden from the list | +| 3.5.3 | In search bar, click "Active" filter to remove it, add "Inactive" filter | Inactive category now visible | + +**3.6 Search and Grouping** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 3.6.1 | Type in search bar, select "Name" search | Filters by name | +| 3.6.2 | Type in search bar, select "Code" search | Filters by code | +| 3.6.3 | Use Filters > Active / Inactive | Filters correctly | +| 3.6.4 | Use Group By > Parent | Categories grouped by parent | + +--- + +**4. Impact Types (Configuration)** + +**4.1 Pre-installed Data** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 4.1.1 | Open **Configuration > Impact Types** | List shows 14 pre-configured types with drag handles for reordering | +| 4.1.2 | Verify Physical types | Displacement, Property Damage, Injury, Death/Fatality | +| 4.1.3 | Verify Economic types | Livelihood Loss, Asset Destruction, Crop Loss, Livestock Loss | +| 4.1.4 | Verify Health types | Illness, Disability, Psychological Impact | +| 4.1.5 | Verify Social types | Family Separation, Community Disruption, Education Disruption | +| 4.1.6 | Check "Category" column | Shows as badge widgets (Physical, Economic, Health, Social) | +| 4.1.7 | Check "Usage" column | Shows 0 for types not yet linked to any impact record | + +**4.2 Create an Impact Type** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 4.2.1 | Click **New** | Form opens | +| 4.2.2 | Enter Name: `Water Contamination`, Code: `WATER_CONTAM`, Category: Health | Fields accept input | +| 4.2.3 | Save | Record saves successfully | + +**4.3 Reorder via Drag** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 4.3.1 | In the list view, grab the drag handle (hamburger icon) on a type | Row becomes draggable | +| 4.3.2 | Drop it at a different position | Sequence updates; reload confirms new order | + +**4.4 Code Uniqueness** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 4.4.1 | Create another type with Code = `WATER_CONTAM` | Error: "An impact type with this code already exists!" | + +**4.5 Search Filters** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 4.5.1 | Use Filters > Physical / Economic / Health / Social | Filters by category correctly | +| 4.5.2 | Use Group By > Category | Types grouped into 4 category sections | + +--- + +**5. Hazard Incidents** + +**5.1 Demo Data** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 5.1.1 | Open **Incidents > All Incidents** | If demo loaded: 3 incidents visible | +| 5.1.2 | "Demo Typhoon Alpha" | Status: Recovery (yellow badge), Severity: Level 4 | +| 5.1.3 | "Demo Flooding Event" | Status: Closed (grey badge), has End Date | +| 5.1.4 | "Demo Drought 2024" | Status: Active (green badge), no End Date | + +**5.2 Create an Incident** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 5.2.1 | Click **New** | Form opens. Status defaults to "Active". Statusbar shows: Alert / Active / Recovery / Closed | +| 5.2.2 | Enter Name: `Test Earthquake`, Code: `TEST-EQ-001` | Fields accept input | +| 5.2.3 | Select Category: search for "Earthquake" | Dropdown shows only active categories, filtered by search text | +| 5.2.4 | Set Start Date: today's date | Date picker works | +| 5.2.5 | Leave End Date empty | Allowed (ongoing incident) | +| 5.2.6 | Set Severity: "Level 3 - Significant" | Selection dropdown works | +| 5.2.7 | Save | Record saves. "Ongoing" ribbon appears (yellow, top-right corner) | + +**5.3 Incident Form Layout** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 5.3.1 | Check the header | "Start Recovery" button visible (since status is Active). "Close Incident" button visible. Status bar shows current step | +| 5.3.2 | Check the button box (top-right) | "Affected" stat button (fa-users icon) always visible. "Areas" stat button visible only if areas > 0 | +| 5.3.3 | Check left column | Code, Category, Severity fields | +| 5.3.4 | Check right column | Start Date, End Date fields | +| 5.3.5 | Check notebook tabs | Three tabs: "Description", "Affected Areas", "Impacts" | +| 5.3.6 | Click "Description" tab | HTML editor for incident description | +| 5.3.7 | Click "Affected Areas" tab | Instruction text visible. Inline-editable list with: Area, Severity Override, Affected Population Estimate, Notes | +| 5.3.8 | Click "Impacts" tab | List showing Registrant, Impact Type, Damage Level, Impact Date, Verification Status | +| 5.3.9 | Scroll to bottom of form | Chatter (message log + activity scheduling) visible below the form | + +**5.4 Code Uniqueness** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 5.4.1 | Create another incident with Code = `TEST-EQ-001` | Error: "An incident with this code already exists!" | + +**5.5 Date Validation** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 5.5.1 | Edit the incident, set End Date before Start Date | Error: "End date must be after start date." | +| 5.5.2 | Set End Date after Start Date, save | Saves successfully. "Ongoing" ribbon disappears (end date is now set) | +| 5.5.3 | Clear End Date, save | "Ongoing" ribbon reappears | + +**5.6 Status Workflow (Lifecycle)** + +Use the incident created in 5.2 (starts as "Active"). + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 5.6.1 | Click **Start Recovery** | Status changes to "Recovery". Button changes to "Set Active". "Close Incident" still visible | +| 5.6.2 | Click **Set Active** | Status returns to "Active". "Start Recovery" button returns | +| 5.6.3 | Click **Start Recovery** again | Status = Recovery | +| 5.6.4 | Click **Close Incident** | Status = Closed. End Date auto-filled with today if it was empty. All fields become readonly | +| 5.6.5 | Check the form in Closed state | No action buttons visible (Set Active, Start Recovery, Close are all hidden). All fields are readonly. Description, areas, and impacts are readonly | + +**5.7 Add Affected Areas** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 5.7.1 | Open a non-closed incident, go to "Affected Areas" tab | Instruction text: "Add areas affected by this incident..." | +| 5.7.2 | Click "Add a line" | New row appears. Area field is a dropdown (no create option) | +| 5.7.3 | Select an Area | Area appears in the row | +| 5.7.4 | Set Severity Override: "Level 5 - Catastrophic" | Selection dropdown works | +| 5.7.5 | Enter Affected Population Estimate: 5000 | Integer field accepts value | +| 5.7.6 | Save | Area link saved. "Areas" stat button now visible with count = 1 | +| 5.7.7 | Try adding the same area again | Error: "This area is already linked to this incident!" | + +**5.8 Stat Buttons** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 5.8.1 | Click "Affected" stat button | Opens Impact Records list filtered to this incident | +| 5.8.2 | Click browser back | Returns to incident form | +| 5.8.3 | Click "Areas" stat button | Opens Area list filtered to linked areas | + +**5.9 Search and Filters (Incident List)** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 5.9.1 | Use search: type a name | Filters by incident name | +| 5.9.2 | Use search: select Code field | Filters by code | +| 5.9.3 | Use search: select Category | Filters by category | +| 5.9.4 | Filters > Alert | Shows only alert-status incidents (blue-tinted rows) | +| 5.9.5 | Filters > Active | Shows only active-status incidents | +| 5.9.6 | Filters > Recovery | Shows only recovery-status incidents (yellow-tinted rows) | +| 5.9.7 | Filters > Closed | Shows only closed incidents (grey/muted rows) | +| 5.9.8 | Filters > Ongoing | Shows incidents with `is_ongoing = True` | +| 5.9.9 | Group By > Status | Groups into status sections | +| 5.9.10 | Group By > Category | Groups by hazard category | +| 5.9.11 | Group By > Severity | Groups by severity level | +| 5.9.12 | Group By > Start Date | Groups by month | + +**5.10 List View Decorations** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 5.10.1 | Check row coloring | Alert rows: blue tint. Recovery rows: yellow tint. Closed rows: grey/muted. Active rows: default (no special coloring) | +| 5.10.2 | Check Status column badges | Alert: blue badge. Active: green badge. Recovery: yellow badge. Closed: grey badge | +| 5.10.3 | Check columns visible | Name, Code, Category, Start Date, End Date (optional), Status, Severity, Areas, Affected | + +--- + +**6. Impact Records** + +**6.1 Create an Impact Record** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 6.1.1 | Navigate to **Incidents > Impact Records**, click **New** | Form opens. Verification Status defaults to "Reported" | +| 6.1.2 | Select Registrant (h1 title area) | Dropdown shows registrants only (`is_registrant = True`). "Create" option not available | +| 6.1.3 | Select Incident (h2 below registrant) | Dropdown shows incidents. "Create" option not available | +| 6.1.4 | Set Impact Type: "Displacement" | Only active impact types shown. "Create" option not available | +| 6.1.5 | Set Damage Level: "Severe" | Selection works (Minimal, Moderate, Severe, Critical, Partially Damaged, Totally Damaged) | +| 6.1.6 | Set Impact Date: same as or after incident start date | Date accepted | +| 6.1.7 | Save | Record saves. Statusbar shows: Reported / Verified / Disputed / Closed | + +**6.2 Impact Date Validation** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 6.2.1 | Edit the impact, set Impact Date before the incident's Start Date | Error: "Impact date cannot be before the incident start date." | + +**6.3 Duplicate Impact Constraint** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 6.3.1 | Create another impact with the same Incident + Registrant + Impact Type | Error: "This registrant already has an impact of this type for this incident!" | +| 6.3.2 | Same Incident + Registrant but different Impact Type (e.g., "Property Damage") | Creates successfully — different types allowed for same registrant | + +**6.4 Verification Workflow** + +Using the impact created in 6.1 (starts as "Reported"): + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 6.4.1 | Check header buttons | "Verify" (primary), "Mark Disputed" (secondary), "Close" (secondary) visible. "Reset to Reported" hidden | +| 6.4.2 | Check "Verification Information" section | Hidden (only shows when not in "Reported" status) | +| 6.4.3 | Click **Verify** | Status → Verified. "Verification Information" section appears showing "Verified By" (current user) and "Verification Date" (now). "Verify" button disappears. "Mark Disputed" and "Close" remain | +| 6.4.4 | Click **Mark Disputed** | Status → Disputed. "Reset to Reported" button appears. "Verify" hidden. "Close" visible | +| 6.4.5 | Click **Reset to Reported** | Status → Reported. Verified By and Verification Date cleared. Back to initial button state | +| 6.4.6 | Click **Close** | Status → Closed. All fields become readonly. Only "Reset to Reported" visible | +| 6.4.7 | Click **Reset to Reported** | Status → Reported again. Fields editable again | + +**6.5 Impact Form Layout** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 6.5.1 | Check title area | Registrant name displayed as h1, Incident name as h2 | +| 6.5.2 | Check left group ("incident_info") | Shows Incident Name (readonly) and Impact Category (readonly) for quick context | +| 6.5.3 | Check right group ("impact_details") | Impact Type, Damage Level, Impact Date fields | +| 6.5.4 | Check "Notes" section | Notes field displayed below verification info with full-width layout | +| 6.5.5 | Check bottom of form | Chatter visible (message log and activities) | + +**6.6 Search and Filters (Impact List)** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 6.6.1 | Search by Incident | Filters to selected incident | +| 6.6.2 | Search by Registrant | Filters to selected registrant | +| 6.6.3 | Search by Impact Type | Filters to selected type | +| 6.6.4 | Filters > Reported / Verified / Disputed / Closed | Each shows correct subset | +| 6.6.5 | Filters > Critical | Shows only `critical` and `totally_damaged` damage levels | +| 6.6.6 | Filters > Severe or Critical | Shows `severe`, `critical`, and `totally_damaged` | +| 6.6.7 | Group By > Incident | Groups by incident | +| 6.6.8 | Group By > Impact Type | Groups by type | +| 6.6.9 | Group By > Damage Level | Groups by severity | +| 6.6.10 | Group By > Verification Status | Groups by status | +| 6.6.11 | Group By > Impact Date | Groups by month | + +**6.7 Impact List Decorations** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 6.7.1 | Check row coloring | Reported: blue. Verified: green. Disputed: yellow. Closed: grey/muted | +| 6.7.2 | Verification Status badges | Same color coding as rows | +| 6.7.3 | Damage Level column | Shows as badge widget (neutral color) | +| 6.7.4 | Optional columns | "Verified By" and "Verified Date" available under column options (hidden by default) | + +--- + +**7. Registrant Integration** + +**7.1 Stat Button** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 7.1.1 | Open a registrant that has NO impact records | No "Impacts" stat button visible in button box | +| 7.1.2 | Open a registrant that HAS impact records | "Impacts" stat button with bolt icon visible, showing count | +| 7.1.3 | Click the stat button | Opens Impact Records list filtered to this registrant | + +**7.2 Emergency Response Tab** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 7.2.1 | Open a registrant with NO impacts | "Emergency Response" tab visible. Tab content shows: "No emergency response records yet. Impact records will appear here when this registrant is affected by a hazard incident." | +| 7.2.2 | Open a registrant with impacts from a CLOSED incident | "Emergency Response" tab shows the "Incident Impacts" section with a readonly list of impacts. No yellow alert banner | +| 7.2.3 | Open a registrant with impacts from an ACTIVE or ALERT incident | Yellow alert banner with warning icon: "Active incident impacts recorded. This registrant is affected by one or more ongoing hazard incidents." Impact list shows below | +| 7.2.4 | Check the impact list columns | Incident, Impact Type, Damage Level (badge), Impact Date, Verification Status (badge with colors) | +| 7.2.5 | Verify the list is readonly | Cannot edit, add, or delete impact records from this view | + +**7.3 Registrant List Extensions** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 7.3.1 | Open the registrants list view | "Impacts" and "Active Impact" columns available in column options (hidden by default) | +| 7.3.2 | Enable both columns via column selector | Columns show correct counts and boolean values | + +**7.4 Registrant Search Extensions** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 7.4.1 | In registrant search, check Filters | "Has Active Impact" and "Has Any Impact" filters available | +| 7.4.2 | Apply "Has Active Impact" | Shows only registrants with impacts from active/alert/recovery incidents | +| 7.4.3 | Apply "Has Any Impact" | Shows all registrants with any impact count > 0 | + +--- + +**8. Security and Access Control** + +**8.1 Viewer Access** + +Log in as the **Viewer** user. + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 8.1.1 | Navigate to Hazard and Emergency | Menu visible. "Incidents" submenu visible | +| 8.1.2 | Check for "Configuration" submenu | NOT visible (restricted to managers) | +| 8.1.3 | Open Incidents > All Incidents | Can view the list and open records | +| 8.1.4 | Try to create a new incident (click New) | Access denied or "New" button not available | +| 8.1.5 | Try to edit an existing incident | Cannot save changes (write access denied) | +| 8.1.6 | Open Incidents > Impact Records | Can view but not create or edit | + +**8.2 Officer Access** + +Log in as the **Officer** user. + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 8.2.1 | Check for "Configuration" submenu | NOT visible (restricted to managers) | +| 8.2.2 | Create a new incident | Succeeds | +| 8.2.3 | Edit an existing incident | Succeeds | +| 8.2.4 | Try to delete an incident | Access denied (officer has no delete permission) | +| 8.2.5 | Create a new impact record | Succeeds | +| 8.2.6 | Edit an existing impact record | Succeeds | +| 8.2.7 | Try to delete an impact record | Access denied | +| 8.2.8 | Open a registrant's Emergency Response tab | Can view impact records (read access) | + +**8.3 Manager Access** + +Log in as the **Manager** user. + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 8.3.1 | Check for "Configuration" submenu | Visible with "Hazard Categories" and "Impact Types" | +| 8.3.2 | Create a hazard category | Succeeds | +| 8.3.3 | Edit a hazard category | Succeeds | +| 8.3.4 | Delete a hazard category (with no linked incidents) | Succeeds | +| 8.3.5 | Try to delete a category linked to an incident | Blocked by `ondelete="restrict"` — error shown | +| 8.3.6 | Create, edit, delete impact types | All succeed | +| 8.3.7 | Create, edit, delete incidents | All succeed | +| 8.3.8 | Create, edit, delete impact records | All succeed | + +--- + +**9. URL Paths (Pretty URLs)** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 9.1 | Navigate to All Incidents | URL contains `/hazard-incidents` | +| 9.2 | Navigate to Impact Records | URL contains `/hazard-impacts` | +| 9.3 | Navigate to Hazard Categories | URL contains `/hazard-categories` | +| 9.4 | Navigate to Impact Types | URL contains `/hazard-impact-types` | + +--- + +**10. Empty States** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 10.1 | Open Incidents list with no records (new database, no demo) | Smiling face icon with text: "Record your first hazard incident" and helper text about tracking events | +| 10.2 | Open Impact Records list with no records | Smiling face icon with text: "Record impacts on registrants" | +| 10.3 | Open Categories list with no records | Smiling face icon with text: "Create your first hazard category" | +| 10.4 | Open Impact Types list with no records | Smiling face icon with text: "Define impact types" | + +--- + +**11. Chatter and Tracking** + +**11.1 Incident Tracking** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 11.1.1 | Create an incident, then change its status | Chatter log shows "Status: Active → Recovery" (or whichever transition) | +| 11.1.2 | Change the severity | Chatter log shows "Severity: Level 2 → Level 4" | +| 11.1.3 | Change the category | Chatter log shows category change | +| 11.1.4 | Post a message in the chatter | Message appears in log | +| 11.1.5 | Schedule an activity | Activity appears in the scheduled activities section | + +**11.2 Impact Tracking** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 11.2.1 | Create an impact, then verify it | Chatter shows "Verification Status: Reported → Verified", "Verified By" set, "Verification Date" set | +| 11.2.2 | Change the damage level | Chatter log shows change | +| 11.2.3 | Post a message | Message appears | + +--- + +**12. Edge Cases** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 12.1 | Create an incident with status "Alert" (create, then note: default is Active — the "alert" status must be set by creating with status alert or changing the initial state) | "Set Active" button visible. "Close Incident" visible. "Start Recovery" NOT visible (only visible for Active) | +| 12.2 | Create an incident, add areas, then close it | All tabs (Description, Affected Areas, Impacts) become readonly. Cannot add or remove areas | +| 12.3 | Close an incident that has no end date | End Date is auto-populated with today's date | +| 12.4 | Close an incident that already has an end date | End Date preserved (not overwritten) | +| 12.5 | Create an impact, set the date to exactly the incident start date | Accepted (boundary case: equal dates are valid) | +| 12.6 | Delete an incident that has geofences linked to it | Incident deletes. Geofence's "Related Incident" field becomes empty (set null) | +| 12.7 | Try to delete an area that is linked to an incident via incident_area_ids | Blocked by `ondelete="restrict"` | + +--- + +**13. Cross-Module: Groups/Households** + +| Step | Action | Expected Result | +|------|--------|-----------------| +| 13.1 | Open a group (household) registrant form | "Emergency Response" tab visible (groups reuse the individual form view) | +| 13.2 | Create an impact record for a group registrant | Impact record creation succeeds; registrant domain allows groups (`is_registrant=True` includes both) | +| 13.3 | View the group's Emergency Response tab | Impact records appear correctly | + +--- + +**Defect Reference** + +All previously known issues from the fix plan (`docs/plans/SPP_HAZARD_FIXES_PLAN.md`) have been resolved. +If you encounter unexpected behavior, please report it as a new issue. + +| ID | Known Limitation | Notes | +|----|-----------------|-------| +| N/A | Badge decorations (severity, damage level) rely on color only | Odoo's badge widget does not support embedded icons. Screen reader users rely on the text label | diff --git a/spp_hazard/security/compliance.yaml b/spp_hazard/security/compliance.yaml index be2b1859..dccdb042 100644 --- a/spp_hazard/security/compliance.yaml +++ b/spp_hazard/security/compliance.yaml @@ -34,19 +34,19 @@ groups: # --- Tier 2: User-facing groups --- - id: group_hazard_viewer tier: 2 - privilege_id: privilege_hazard_viewer + privilege_id: privilege_hazard implied_ids: [group_hazard_read] comment: "Can view hazard incidents, categories, and impacts but cannot modify." - id: group_hazard_officer tier: 2 - privilege_id: privilege_hazard_officer + privilege_id: privilege_hazard implied_ids: [group_hazard_create, group_hazard_viewer] comment: "Can create and manage hazard incidents and impacts." - id: group_hazard_manager tier: 2 - privilege_id: privilege_hazard_manager + privilege_id: privilege_hazard implied_ids: [group_hazard_officer] comment: "Full access to hazard management including configuration." diff --git a/spp_hazard/security/groups.xml b/spp_hazard/security/groups.xml index 3341b804..1dcfe275 100644 --- a/spp_hazard/security/groups.xml +++ b/spp_hazard/security/groups.xml @@ -27,16 +27,12 @@ + - - Viewer - - - Viewer - + Can view hazard incidents, categories, and impacts but cannot modify. @@ -44,14 +40,9 @@ - - Officer - - - Officer - + Can create and manage hazard incidents and impacts. @@ -65,14 +56,9 @@ - - Manager - - - Manager - + Full access to hazard management including configuration. diff --git a/spp_hazard/security/ir.model.access.csv b/spp_hazard/security/ir.model.access.csv index de21cff0..67d51525 100644 --- a/spp_hazard/security/ir.model.access.csv +++ b/spp_hazard/security/ir.model.access.csv @@ -3,6 +3,7 @@ access_spp_hazard_category_user,spp.hazard.category user,model_spp_hazard_catego access_spp_hazard_incident_user,spp.hazard.incident user,model_spp_hazard_incident,base.group_user,1,0,0,0 access_spp_hazard_incident_area_user,spp.hazard.incident.area user,model_spp_hazard_incident_area,base.group_user,1,0,0,0 access_spp_hazard_impact_type_user,spp.hazard.impact.type user,model_spp_hazard_impact_type,base.group_user,1,0,0,0 +access_spp_hazard_impact_user,spp.hazard.impact user,model_spp_hazard_impact,base.group_user,1,0,0,0 access_spp_hazard_category_sysadmin,Hazard Category System Admin,model_spp_hazard_category,base.group_system,1,1,1,1 access_spp_hazard_incident_sysadmin,Hazard Incident System Admin,model_spp_hazard_incident,base.group_system,1,1,1,1 access_spp_hazard_incident_area_sysadmin,Hazard Incident Area System Admin,model_spp_hazard_incident_area,base.group_system,1,1,1,1 @@ -18,25 +19,25 @@ access_spp_hazard_incident_read,Hazard Incident Read,model_spp_hazard_incident,g access_spp_hazard_incident_area_read,Hazard Incident Area Read,model_spp_hazard_incident_area,group_hazard_read,1,0,0,0 access_spp_hazard_impact_type_read,Hazard Impact Type Read,model_spp_hazard_impact_type,group_hazard_read,1,0,0,0 access_spp_hazard_impact_read,Hazard Impact Read,model_spp_hazard_impact,group_hazard_read,1,0,0,0 -access_spp_hazard_category_write,Hazard Category Write,model_spp_hazard_category,group_hazard_write,1,1,0,0 +access_spp_hazard_category_write,Hazard Category Write,model_spp_hazard_category,group_hazard_write,1,0,0,0 access_spp_hazard_incident_write,Hazard Incident Write,model_spp_hazard_incident,group_hazard_write,1,1,0,0 access_spp_hazard_incident_area_write,Hazard Incident Area Write,model_spp_hazard_incident_area,group_hazard_write,1,1,0,0 -access_spp_hazard_impact_type_write,Hazard Impact Type Write,model_spp_hazard_impact_type,group_hazard_write,1,1,0,0 +access_spp_hazard_impact_type_write,Hazard Impact Type Write,model_spp_hazard_impact_type,group_hazard_write,1,0,0,0 access_spp_hazard_impact_write,Hazard Impact Write,model_spp_hazard_impact,group_hazard_write,1,1,0,0 -access_spp_hazard_category_create,Hazard Category Create,model_spp_hazard_category,group_hazard_create,1,1,1,0 +access_spp_hazard_category_create,Hazard Category Create,model_spp_hazard_category,group_hazard_create,1,0,0,0 access_spp_hazard_incident_create,Hazard Incident Create,model_spp_hazard_incident,group_hazard_create,1,1,1,0 access_spp_hazard_incident_area_create,Hazard Incident Area Create,model_spp_hazard_incident_area,group_hazard_create,1,1,1,0 -access_spp_hazard_impact_type_create,Hazard Impact Type Create,model_spp_hazard_impact_type,group_hazard_create,1,1,1,0 +access_spp_hazard_impact_type_create,Hazard Impact Type Create,model_spp_hazard_impact_type,group_hazard_create,1,0,0,0 access_spp_hazard_impact_create,Hazard Impact Create,model_spp_hazard_impact,group_hazard_create,1,1,1,0 access_spp_hazard_category_manager,Hazard Category Manager,model_spp_hazard_category,group_hazard_manager,1,1,1,1 access_spp_hazard_incident_manager,Hazard Incident Manager,model_spp_hazard_incident,group_hazard_manager,1,1,1,1 access_spp_hazard_incident_area_manager,Hazard Incident Area Manager,model_spp_hazard_incident_area,group_hazard_manager,1,1,1,1 access_spp_hazard_impact_type_manager,Hazard Impact Type Manager,model_spp_hazard_impact_type,group_hazard_manager,1,1,1,1 access_spp_hazard_impact_manager,Hazard Impact Manager,model_spp_hazard_impact,group_hazard_manager,1,1,1,1 -access_spp_hazard_category_officer,Hazard Category Officer,model_spp_hazard_category,group_hazard_officer,1,1,1,0 +access_spp_hazard_category_officer,Hazard Category Officer,model_spp_hazard_category,group_hazard_officer,1,0,0,0 access_spp_hazard_incident_officer,Hazard Incident Officer,model_spp_hazard_incident,group_hazard_officer,1,1,1,0 access_spp_hazard_incident_area_officer,Hazard Incident Area Officer,model_spp_hazard_incident_area,group_hazard_officer,1,1,1,0 -access_spp_hazard_impact_type_officer,Hazard Impact Type Officer,model_spp_hazard_impact_type,group_hazard_officer,1,1,1,0 +access_spp_hazard_impact_type_officer,Hazard Impact Type Officer,model_spp_hazard_impact_type,group_hazard_officer,1,0,0,0 access_spp_hazard_impact_officer,Hazard Impact Officer,model_spp_hazard_impact,group_hazard_officer,1,1,1,0 access_spp_hazard_category_viewer,Hazard Category Viewer,model_spp_hazard_category,group_hazard_viewer,1,0,0,0 access_spp_hazard_incident_viewer,Hazard Incident Viewer,model_spp_hazard_incident,group_hazard_viewer,1,0,0,0 diff --git a/spp_hazard/security/privileges.xml b/spp_hazard/security/privileges.xml new file mode 100644 index 00000000..4c95e252 --- /dev/null +++ b/spp_hazard/security/privileges.xml @@ -0,0 +1,8 @@ + + + + + Hazard + + + diff --git a/spp_hazard/static/description/index.html b/spp_hazard/static/description/index.html index c4bd810a..1322297e 100644 --- a/spp_hazard/static/description/index.html +++ b/spp_hazard/static/description/index.html @@ -370,10 +370,12 @@

OpenSPP Hazard & Emergency Management

!! source digest: sha256:6c799a070608a2c41e92173cb6e13783cda3e3e01c784f1d94decc3c6391a182 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: LGPL-3 OpenSPP/OpenSPP2

-

Records disaster events and tracks their impact on individual -registrants. Supports hierarchical hazard classification, geographic -scope tracking, severity levels, and verification workflows to enable -targeted emergency response and humanitarian assistance.

+

Records disaster events and tracks their impact on registrants. Provides +hierarchical hazard classification, geographic scope tracking via areas +and GIS geofences, severity levels, and verification workflows to enable +targeted emergency response and humanitarian assistance. Incident and +impact records include full chatter integration for audit trails and +activity scheduling.

Key Capabilities

    @@ -383,6 +385,8 @@

    Key Capabilities

    status (alert, active, recovery, closed)
  • Link incidents to geographic areas with area-specific severity overrides
  • +
  • Define hazard zone geofences linked to specific incidents via +spp.gis.geofence extension
  • Track registrant-level impacts by type (physical, economic, health, social) and damage level
  • Verify impact records with workflow states (reported, verified, @@ -396,8 +400,8 @@

    Key Capabilities

    Key Models

    --++ @@ -406,28 +410,34 @@

    Key Models

    - + - + +area-specific severity override - + - + + + +
    Model
    spp.hazard.categoryHierarchical classification of hazard -typesHierarchical classification of +hazard types
    spp.hazard.incidentSpecific disaster event with dates, -severity, and affected areasSpecific disaster event with +dates, severity, and affected +areas
    spp.hazard.incident.area Links incident to area with -area-specific details
    spp.hazard.impactRecords impact on a registrant (type, -damage level, verification)Records impact on a registrant +(type, damage level, +verification)
    spp.hazard.impact.type Classification of impact types by category
    res.partner (extended)Adds hazard impact tracking fields to -registrantsAdds hazard impact tracking +fields to registrants
    spp.gis.geofence (extended)Adds hazard_zone geofence +type and incident linking
    @@ -436,11 +446,11 @@

    Key Models

    Configuration

    After installing:

      -
    1. Navigate to Hazard & Emergency > Configuration > Hazard +
    2. Navigate to Hazard and Emergency > Configuration > Hazard Categories
    3. Create or review hierarchical hazard categories (e.g., Natural Disasters, Man-made Disasters)
    4. -
    5. Navigate to Hazard & Emergency > Configuration > Impact Types
    6. +
    7. Navigate to Hazard and Emergency > Configuration > Impact Types
    8. Review pre-configured impact types (Displacement, Property Damage, Injury, etc.) or create custom types
    @@ -448,10 +458,10 @@

    Configuration

    UI Location

      -
    • Menu: Hazard & Emergency (top-level application menu)
    • -
    • Incidents: Hazard & Emergency > Incidents > All Incidents
    • -
    • Impacts: Hazard & Emergency > Incidents > Impact Records
    • -
    • Configuration: Hazard & Emergency > Configuration (accessible to +
    • Menu: Hazard and Emergency (top-level application menu)
    • +
    • Incidents: Hazard and Emergency > Incidents > All Incidents
    • +
    • Impacts: Hazard and Emergency > Incidents > Impact Records
    • +
    • Configuration: Hazard and Emergency > Configuration (accessible to managers only)
    • Registrant Form: Stat button shows impact count; “Emergency Response” tab displays impact records list
    • @@ -461,8 +471,8 @@

      UI Location

      Security

      --++ @@ -475,12 +485,12 @@

      Security

      records - +configuration models @@ -498,24 +508,1977 @@

      Extension Points

      (e.g., crop damage for farmer registries)
    • Override bulk_create_impacts() to customize mass impact record creation
    • +
    • Extend spp.gis.geofence to add behavior for hazard_zone +geofence type
    • Dependencies

      -

      base, spp_security, spp_registry, spp_area

      +

      base, spp_security, spp_registry, spp_area, spp_gis

      Table of contents

      +
      +

      Usage

      +

      Testing guide for QA validation of the OpenSPP Hazard and Emergency +Management module.

      +

      Prerequisites

      +
        +
      1. OpenSPP instance running with spp_hazard installed (and demo data +loaded)
      2. +
      3. Three test users configured with different security groups:
          +
        • Admin user — system administrator (has all access)
        • +
        • Manager user — assigned to Hazard: Manager group
        • +
        • Officer user — assigned to Hazard: Officer group
        • +
        • Viewer user — assigned to Hazard: Viewer group
        • +
        +
      4. +
      5. At least one area record in spp.area (created via Registry > +Areas)
      6. +
      7. At least one registrant (individual) with an area assigned
      8. +
      +

      To assign hazard groups: Settings > Users & Companies > Users > +(select user) > Access Rights tab > Hazard section — choose Viewer, +Officer, or Manager.

      +
      +

      1. Module Installation

      +
      Group
      group_hazard_officerCreate and manage incidents and +Read/write/create incidents and impacts (no delete)
      group_hazard_manager Full CRUD access including -configuration
      spp_security.group_spp_admin Inherits manager access
      +++++ + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      1.1Navigate to Apps, search +for “Hazard”“OpenSPP Hazard & Emergency +Management” appears
      1.2Install the moduleInstallation completes +without errors
      1.3Refresh the browser“Hazard and Emergency” +appears in the top +application menu bar
      +
      +

      2. Menu Structure

      +

      Log in as Admin or Manager.

      + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      2.1Click Hazard and +Emergency in the top menuSubmenu appears with +“Incidents” and +“Configuration” sections
      2.2Click Incidents > All +IncidentsIncident list view loads +(URL ends with +/hazard-incidents)
      2.3Click Incidents > Impact +RecordsImpact list view loads (URL +ends with +/hazard-impacts)
      2.4Click Configuration > +Hazard CategoriesCategory list loads with +pre-seeded Active filter +(URL: +/hazard-categories)
      2.5Click Configuration > +Impact TypesImpact type list loads with +Active filter (URL: +/hazard-impact-types)
      +
      +

      3. Hazard Categories (Configuration)

      +

      3.1 Pre-installed Data (Demo)

      + +++++ + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      3.1.1Open Configuration > +Hazard CategoriesList shows categories. If +demo data loaded: “Natural +Disaster”, “Storm”, +“Typhoon”, “Geological”, +“Earthquake”, “Volcanic +Eruption”, “Hydrological”, +“Flood”, “Climate”, +“Drought”, “Health +Emergency”, “Pandemic”, +“Disease Outbreak”, +“Conflict”, “Armed +Conflict”, “Economic +Shock”, “Food Crisis”
      3.1.2Check the “Complete Name” +columnHierarchical names shown, +e.g., “Natural Disaster > +Storm > Typhoon”
      3.1.3Check the “Incidents” +columnShows integer count for +each category
      +

      3.2 Create a Category

      + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      3.2.1Click NewForm opens with Name field +focused
      3.2.2Enter Name: +Test Wildfire, Code: +TEST_WILDFIREFields accept input
      3.2.3Set Parent Category to +“Natural Disaster”Dropdown shows only active +categories
      3.2.4SaveRecord saves. Complete Name +shows “Natural Disaster > +Test Wildfire”
      3.2.5Navigate to the parent +“Natural Disaster” record“Subcategories” section is +visible below the form and +lists “Test Wildfire”
      +

      3.3 Category Incident Count

      + +++++ + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      3.3.1Open a category that has +linked incidents (e.g., +“Typhoon” if demo data +loaded)“Number of Incidents” field +shows the count in the +right column
      3.3.2Verify the count matches +the actual number of +incidentsCount should be accurate
      +

      3.4 Category Code Uniqueness

      + +++++ + + + + + + + + + + + + +
      StepActionExpected Result
      3.4.1Create a new category with +Code = TEST_WILDFIRE +(same as 3.2)Error: “A hazard category +with this code already +exists!”
      +

      3.5 Deactivation

      + +++++ + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      3.5.1Open a category, uncheck +“Active”, saveRed “Inactive” ribbon +appears at top-right of +form
      3.5.2Return to the list (Active +filter is on by default)Inactive category is hidden +from the list
      3.5.3In search bar, click +“Active” filter to remove +it, add “Inactive” filterInactive category now +visible
      +

      3.6 Search and Grouping

      + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      3.6.1Type in search bar, select +“Name” searchFilters by name
      3.6.2Type in search bar, select +“Code” searchFilters by code
      3.6.3Use Filters > Active / +InactiveFilters correctly
      3.6.4Use Group By > ParentCategories grouped by +parent
      +
      +

      4. Impact Types (Configuration)

      +

      4.1 Pre-installed Data

      + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      4.1.1Open Configuration > +Impact TypesList shows 14 +pre-configured types with +drag handles for reordering
      4.1.2Verify Physical typesDisplacement, Property +Damage, Injury, +Death/Fatality
      4.1.3Verify Economic typesLivelihood Loss, Asset +Destruction, Crop Loss, +Livestock Loss
      4.1.4Verify Health typesIllness, Disability, +Psychological Impact
      4.1.5Verify Social typesFamily Separation, +Community Disruption, +Education Disruption
      4.1.6Check “Category” columnShows as badge widgets +(Physical, Economic, +Health, Social)
      4.1.7Check “Usage” columnShows 0 for types not yet +linked to any impact record
      +

      4.2 Create an Impact Type

      + +++++ + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      4.2.1Click NewForm opens
      4.2.2Enter Name: +Water Contamination, +Code: WATER_CONTAM, +Category: HealthFields accept input
      4.2.3SaveRecord saves successfully
      +

      4.3 Reorder via Drag

      + +++++ + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      4.3.1In the list view, grab the +drag handle (hamburger +icon) on a typeRow becomes draggable
      4.3.2Drop it at a different +positionSequence updates; reload +confirms new order
      +

      4.4 Code Uniqueness

      + +++++ + + + + + + + + + + + + +
      StepActionExpected Result
      4.4.1Create another type with +Code = WATER_CONTAMError: “An impact type with +this code already exists!”
      +

      4.5 Search Filters

      + +++++ + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      4.5.1Use Filters > Physical / +Economic / Health / SocialFilters by category +correctly
      4.5.2Use Group By > CategoryTypes grouped into 4 +category sections
      +
      +

      5. Hazard Incidents

      +

      5.1 Demo Data

      + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      5.1.1Open Incidents > All +IncidentsIf demo loaded: 3 incidents +visible
      5.1.2“Demo Typhoon Alpha”Status: Recovery (yellow +badge), Severity: Level 4
      5.1.3“Demo Flooding Event”Status: Closed (grey +badge), has End Date
      5.1.4“Demo Drought 2024”Status: Active (green +badge), no End Date
      +

      5.2 Create an Incident

      + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      5.2.1Click NewForm opens. Status defaults +to “Active”. Statusbar +shows: Alert / Active / +Recovery / Closed
      5.2.2Enter Name: +Test Earthquake, Code: +TEST-EQ-001Fields accept input
      5.2.3Select Category: search for +“Earthquake”Dropdown shows only active +categories, filtered by +search text
      5.2.4Set Start Date: today’s +dateDate picker works
      5.2.5Leave End Date emptyAllowed (ongoing incident)
      5.2.6Set Severity: “Level 3 - +Significant”Selection dropdown works
      5.2.7SaveRecord saves. “Ongoing” +ribbon appears (yellow, +top-right corner)
      +

      5.3 Incident Form Layout

      + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      5.3.1Check the header“Start Recovery” button +visible (since status is +Active). “Close Incident” +button visible. Status bar +shows current step
      5.3.2Check the button box +(top-right)“Affected” stat button +(fa-users icon) always +visible. “Areas” stat +button visible only if +areas > 0
      5.3.3Check left columnCode, Category, Severity +fields
      5.3.4Check right columnStart Date, End Date fields
      5.3.5Check notebook tabsThree tabs: “Description”, +“Affected Areas”, “Impacts”
      5.3.6Click “Description” tabHTML editor for incident +description
      5.3.7Click “Affected Areas” tabInstruction text visible. +Inline-editable list with: +Area, Severity Override, +Affected Population +Estimate, Notes
      5.3.8Click “Impacts” tabList showing Registrant, +Impact Type, Damage Level, +Impact Date, Verification +Status
      5.3.9Scroll to bottom of formChatter (message log + +activity scheduling) +visible below the form
      +

      5.4 Code Uniqueness

      + +++++ + + + + + + + + + + + + +
      StepActionExpected Result
      5.4.1Create another incident +with Code = TEST-EQ-001Error: “An incident with +this code already exists!”
      +

      5.5 Date Validation

      + +++++ + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      5.5.1Edit the incident, set End +Date before Start DateError: “End date must be +after start date.”
      5.5.2Set End Date after Start +Date, saveSaves successfully. +“Ongoing” ribbon disappears +(end date is now set)
      5.5.3Clear End Date, save“Ongoing” ribbon reappears
      +

      5.6 Status Workflow (Lifecycle)

      +

      Use the incident created in 5.2 (starts as “Active”).

      + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      5.6.1Click Start RecoveryStatus changes to +“Recovery”. Button changes +to “Set Active”. “Close +Incident” still visible
      5.6.2Click Set ActiveStatus returns to “Active”. +“Start Recovery” button +returns
      5.6.3Click Start Recovery +againStatus = Recovery
      5.6.4Click Close IncidentStatus = Closed. End Date +auto-filled with today if +it was empty. All fields +become readonly
      5.6.5Check the form in Closed +stateNo action buttons visible +(Set Active, Start +Recovery, Close are all +hidden). All fields are +readonly. Description, +areas, and impacts are +readonly
      +

      5.7 Add Affected Areas

      + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      5.7.1Open a non-closed incident, +go to “Affected Areas” tabInstruction text: “Add +areas affected by this +incident…”
      5.7.2Click “Add a line”New row appears. Area field +is a dropdown (no create +option)
      5.7.3Select an AreaArea appears in the row
      5.7.4Set Severity Override: +“Level 5 - Catastrophic”Selection dropdown works
      5.7.5Enter Affected Population +Estimate: 5000Integer field accepts value
      5.7.6SaveArea link saved. “Areas” +stat button now visible +with count = 1
      5.7.7Try adding the same area +againError: “This area is +already linked to this +incident!”
      +

      5.8 Stat Buttons

      + +++++ + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      5.8.1Click “Affected” stat +buttonOpens Impact Records list +filtered to this incident
      5.8.2Click browser backReturns to incident form
      5.8.3Click “Areas” stat buttonOpens Area list filtered to +linked areas
      +

      5.9 Search and Filters (Incident List)

      + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      5.9.1Use search: type a nameFilters by incident name
      5.9.2Use search: select Code +fieldFilters by code
      5.9.3Use search: select CategoryFilters by category
      5.9.4Filters > AlertShows only alert-status +incidents (blue-tinted +rows)
      5.9.5Filters > ActiveShows only active-status +incidents
      5.9.6Filters > RecoveryShows only recovery-status +incidents (yellow-tinted +rows)
      5.9.7Filters > ClosedShows only closed incidents +(grey/muted rows)
      5.9.8Filters > OngoingShows incidents with +is_ongoing = True
      5.9.9Group By > StatusGroups into status sections
      5.9.10Group By > CategoryGroups by hazard category
      5.9.11Group By > SeverityGroups by severity level
      5.9.12Group By > Start DateGroups by month
      +

      5.10 List View Decorations

      + +++++ + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      5.10.1Check row coloringAlert rows: blue tint. +Recovery rows: yellow tint. +Closed rows: grey/muted. +Active rows: default (no +special coloring)
      5.10.2Check Status column badgesAlert: blue badge. Active: +green badge. Recovery: +yellow badge. Closed: grey +badge
      5.10.3Check columns visibleName, Code, Category, Start +Date, End Date (optional), +Status, Severity, Areas, +Affected
      +
      +

      6. Impact Records

      +

      6.1 Create an Impact Record

      + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      6.1.1Navigate to Incidents > +Impact Records, click +NewForm opens. Verification +Status defaults to +“Reported”
      6.1.2Select Registrant (h1 title +area)Dropdown shows registrants +only +(is_registrant = True). +“Create” option not +available
      6.1.3Select Incident (h2 below +registrant)Dropdown shows incidents. +“Create” option not +available
      6.1.4Set Impact Type: +“Displacement”Only active impact types +shown. “Create” option not +available
      6.1.5Set Damage Level: “Severe”Selection works (Minimal, +Moderate, Severe, Critical, +Partially Damaged, Totally +Damaged)
      6.1.6Set Impact Date: same as or +after incident start dateDate accepted
      6.1.7SaveRecord saves. Statusbar +shows: Reported / Verified +/ Disputed / Closed
      +

      6.2 Impact Date Validation

      + +++++ + + + + + + + + + + + + +
      StepActionExpected Result
      6.2.1Edit the impact, set Impact +Date before the incident’s +Start DateError: “Impact date cannot +be before the incident +start date.”
      +

      6.3 Duplicate Impact Constraint

      + +++++ + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      6.3.1Create another impact with +the same Incident + +Registrant + Impact TypeError: “This registrant +already has an impact of +this type for this +incident!”
      6.3.2Same Incident + Registrant +but different Impact Type +(e.g., “Property Damage”)Creates successfully — +different types allowed for +same registrant
      +

      6.4 Verification Workflow

      +

      Using the impact created in 6.1 (starts as “Reported”):

      + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      6.4.1Check header buttons“Verify” (primary), “Mark +Disputed” (secondary), +“Close” (secondary) +visible. “Reset to +Reported” hidden
      6.4.2Check “Verification +Information” sectionHidden (only shows when not +in “Reported” status)
      6.4.3Click VerifyStatus → Verified. +“Verification Information” +section appears showing +“Verified By” (current +user) and “Verification +Date” (now). “Verify” +button disappears. “Mark +Disputed” and “Close” +remain
      6.4.4Click Mark DisputedStatus → Disputed. “Reset +to Reported” button +appears. “Verify” hidden. +“Close” visible
      6.4.5Click Reset to ReportedStatus → Reported. Verified +By and Verification Date +cleared. Back to initial +button state
      6.4.6Click CloseStatus → Closed. All fields +become readonly. Only +“Reset to Reported” visible
      6.4.7Click Reset to ReportedStatus → Reported again. +Fields editable again
      +

      6.5 Impact Form Layout

      + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      6.5.1Check title areaRegistrant name displayed +as h1, Incident name as h2
      6.5.2Check left group +(“incident_info”)Shows Incident Name +(readonly) and Impact +Category (readonly) for +quick context
      6.5.3Check right group +(“impact_details”)Impact Type, Damage Level, +Impact Date fields
      6.5.4Check “Notes” sectionNotes field displayed below +verification info with +full-width layout
      6.5.5Check bottom of formChatter visible (message +log and activities)
      +

      6.6 Search and Filters (Impact List)

      + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      6.6.1Search by IncidentFilters to selected +incident
      6.6.2Search by RegistrantFilters to selected +registrant
      6.6.3Search by Impact TypeFilters to selected type
      6.6.4Filters > Reported / +Verified / Disputed / +ClosedEach shows correct subset
      6.6.5Filters > CriticalShows only critical and +totally_damaged damage +levels
      6.6.6Filters > Severe or +CriticalShows severe, +critical, and +totally_damaged
      6.6.7Group By > IncidentGroups by incident
      6.6.8Group By > Impact TypeGroups by type
      6.6.9Group By > Damage LevelGroups by severity
      6.6.10Group By > Verification +StatusGroups by status
      6.6.11Group By > Impact DateGroups by month
      +

      6.7 Impact List Decorations

      + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      6.7.1Check row coloringReported: blue. Verified: +green. Disputed: yellow. +Closed: grey/muted
      6.7.2Verification Status badgesSame color coding as rows
      6.7.3Damage Level columnShows as badge widget +(neutral color)
      6.7.4Optional columns“Verified By” and “Verified +Date” available under +column options (hidden by +default)
      +
      +

      7. Registrant Integration

      +

      7.1 Stat Button

      + +++++ + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      7.1.1Open a registrant that has +NO impact recordsNo “Impacts” stat button +visible in button box
      7.1.2Open a registrant that HAS +impact records“Impacts” stat button with +bolt icon visible, showing +count
      7.1.3Click the stat buttonOpens Impact Records list +filtered to this registrant
      +

      7.2 Emergency Response Tab

      + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      7.2.1Open a registrant with NO +impacts“Emergency Response” tab +visible. Tab content shows: +“No emergency response +records yet. Impact records +will appear here when this +registrant is affected by a +hazard incident.”
      7.2.2Open a registrant with +impacts from a CLOSED +incident“Emergency Response” tab +shows the “Incident +Impacts” section with a +readonly list of impacts. +No yellow alert banner
      7.2.3Open a registrant with +impacts from an ACTIVE or +ALERT incidentYellow alert banner with +warning icon: “Active +incident impacts recorded. +This registrant is affected +by one or more ongoing +hazard incidents.” Impact +list shows below
      7.2.4Check the impact list +columnsIncident, Impact Type, +Damage Level (badge), +Impact Date, Verification +Status (badge with colors)
      7.2.5Verify the list is readonlyCannot edit, add, or delete +impact records from this +view
      +

      7.3 Registrant List Extensions

      + +++++ + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      7.3.1Open the registrants list +view“Impacts” and “Active +Impact” columns available +in column options (hidden +by default)
      7.3.2Enable both columns via +column selectorColumns show correct counts +and boolean values
      +

      7.4 Registrant Search Extensions

      + +++++ + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      7.4.1In registrant search, check +Filters“Has Active Impact” and +“Has Any Impact” filters +available
      7.4.2Apply “Has Active Impact”Shows only registrants with +impacts from +active/alert/recovery +incidents
      7.4.3Apply “Has Any Impact”Shows all registrants with +any impact count > 0
      +
      +

      8. Security and Access Control

      +

      8.1 Viewer Access

      +

      Log in as the Viewer user.

      + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      8.1.1Navigate to Hazard and +EmergencyMenu visible. “Incidents” +submenu visible
      8.1.2Check for “Configuration” +submenuNOT visible (restricted to +managers)
      8.1.3Open Incidents > All +IncidentsCan view the list and open +records
      8.1.4Try to create a new +incident (click New)Access denied or “New” +button not available
      8.1.5Try to edit an existing +incidentCannot save changes (write +access denied)
      8.1.6Open Incidents > Impact +RecordsCan view but not create or +edit
      +

      8.2 Officer Access

      +

      Log in as the Officer user.

      + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      8.2.1Check for “Configuration” +submenuNOT visible (restricted to +managers)
      8.2.2Create a new incidentSucceeds
      8.2.3Edit an existing incidentSucceeds
      8.2.4Try to delete an incidentAccess denied (officer has +no delete permission)
      8.2.5Create a new impact recordSucceeds
      8.2.6Edit an existing impact +recordSucceeds
      8.2.7Try to delete an impact +recordAccess denied
      8.2.8Open a registrant’s +Emergency Response tabCan view impact records +(read access)
      +

      8.3 Manager Access

      +

      Log in as the Manager user.

      + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      8.3.1Check for “Configuration” +submenuVisible with “Hazard +Categories” and “Impact +Types”
      8.3.2Create a hazard categorySucceeds
      8.3.3Edit a hazard categorySucceeds
      8.3.4Delete a hazard category +(with no linked incidents)Succeeds
      8.3.5Try to delete a category +linked to an incidentBlocked by +ondelete="restrict" — +error shown
      8.3.6Create, edit, delete impact +typesAll succeed
      8.3.7Create, edit, delete +incidentsAll succeed
      8.3.8Create, edit, delete impact +recordsAll succeed
      +
      +

      9. URL Paths (Pretty URLs)

      + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      9.1Navigate to All IncidentsURL contains /hazard-incidents
      9.2Navigate to Impact RecordsURL contains /hazard-impacts
      9.3Navigate to Hazard CategoriesURL contains /hazard-categories
      9.4Navigate to Impact TypesURL contains /hazard-impact-types
      +
      +

      10. Empty States

      + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      10.1Open Incidents list with no +records (new database, no +demo)Smiling face icon with text: +“Record your first hazard +incident” and helper text +about tracking events
      10.2Open Impact Records list +with no recordsSmiling face icon with text: +“Record impacts on +registrants”
      10.3Open Categories list with no +recordsSmiling face icon with text: +“Create your first hazard +category”
      10.4Open Impact Types list with +no recordsSmiling face icon with text: +“Define impact types”
      +
      +

      11. Chatter and Tracking

      +

      11.1 Incident Tracking

      + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      11.1.1Create an incident, then +change its statusChatter log shows “Status: +Active → Recovery” (or +whichever transition)
      11.1.2Change the severityChatter log shows +“Severity: Level 2 → Level +4”
      11.1.3Change the categoryChatter log shows category +change
      11.1.4Post a message in the +chatterMessage appears in log
      11.1.5Schedule an activityActivity appears in the +scheduled activities +section
      +

      11.2 Impact Tracking

      + +++++ + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      11.2.1Create an impact, then +verify itChatter shows “Verification +Status: Reported → +Verified”, “Verified By” +set, “Verification Date” +set
      11.2.2Change the damage levelChatter log shows change
      11.2.3Post a messageMessage appears
      +
      +

      12. Edge Cases

      + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      12.1Create an incident with +status “Alert” (create, then +note: default is Active — +the “alert” status must be +set by creating with status +alert or changing the +initial state)“Set Active” button visible. +“Close Incident” visible. +“Start Recovery” NOT visible +(only visible for Active)
      12.2Create an incident, add +areas, then close itAll tabs (Description, +Affected Areas, Impacts) +become readonly. Cannot add +or remove areas
      12.3Close an incident that has +no end dateEnd Date is auto-populated +with today’s date
      12.4Close an incident that +already has an end dateEnd Date preserved (not +overwritten)
      12.5Create an impact, set the +date to exactly the incident +start dateAccepted (boundary case: +equal dates are valid)
      12.6Delete an incident that has +geofences linked to itIncident deletes. Geofence’s +“Related Incident” field +becomes empty (set null)
      12.7Try to delete an area that +is linked to an incident via +incident_area_idsBlocked by +ondelete="restrict"
      +
      +

      13. Cross-Module: Groups/Households

      + +++++ + + + + + + + + + + + + + + + + + + + + +
      StepActionExpected Result
      13.1Open a group (household) +registrant form“Emergency Response” tab +visible (groups reuse the +individual form view)
      13.2Create an impact record for +a group registrantImpact record creation +succeeds; registrant domain +allows groups +(is_registrant=True +includes both)
      13.3View the group’s Emergency +Response tabImpact records appear +correctly
      +
      +

      Defect Reference

      +

      All previously known issues from the fix plan +(docs/plans/SPP_HAZARD_FIXES_PLAN.md) have been resolved. If you +encounter unexpected behavior, please report it as a new issue.

      + +++++ + + + + + + + + + + + + +
      IDKnown LimitationNotes
      N/ABadge decorations (severity, +damage level) rely on color +onlyOdoo’s badge widget does not +support embedded icons. +Screen reader users rely on +the text label
      +
    -

    Bug Tracker

    +

    Bug Tracker

    Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed @@ -523,15 +2486,15 @@

    Bug Tracker

    Do not contact contributors directly about support or help with technical issues.

    -

    Credits

    +

    Credits

    -

    Authors

    +

    Authors

    • OpenSPP.org
    -

    Maintainers

    +

    Maintainers

    Current maintainers:

    jeremi gonzalesedwin1123 reichie020212 emjay0921

    This module is part of the OpenSPP/OpenSPP2 project on GitHub.

    diff --git a/spp_hazard/tests/__init__.py b/spp_hazard/tests/__init__.py index e0c78588..9956de3b 100644 --- a/spp_hazard/tests/__init__.py +++ b/spp_hazard/tests/__init__.py @@ -5,3 +5,4 @@ from . import test_hazard_impact from . import test_hazard_impact_type from . import test_geofence +from . import test_registrant diff --git a/spp_hazard/tests/common.py b/spp_hazard/tests/common.py index 16ac4342..baef332d 100644 --- a/spp_hazard/tests/common.py +++ b/spp_hazard/tests/common.py @@ -1,5 +1,6 @@ # Part of OpenSPP. See LICENSE file for full copyright and licensing details. +from odoo import Command from odoo.tests.common import TransactionCase @@ -68,3 +69,36 @@ def setUpClass(cls): "category": "physical", } ) + + # Create test users for security tests + base_group = Command.link(cls.env.ref("base.group_user").id) + cls.hazard_viewer = cls.env["res.users"].create( + { + "name": "Hazard Viewer", + "login": "hazard_viewer_test", + "group_ids": [ + base_group, + Command.link(cls.env.ref("spp_hazard.group_hazard_viewer").id), + ], + } + ) + cls.hazard_officer = cls.env["res.users"].create( + { + "name": "Hazard Officer", + "login": "hazard_officer_test", + "group_ids": [ + base_group, + Command.link(cls.env.ref("spp_hazard.group_hazard_officer").id), + ], + } + ) + cls.hazard_manager = cls.env["res.users"].create( + { + "name": "Hazard Manager", + "login": "hazard_manager_test", + "group_ids": [ + base_group, + Command.link(cls.env.ref("spp_hazard.group_hazard_manager").id), + ], + } + ) diff --git a/spp_hazard/tests/test_geofence.py b/spp_hazard/tests/test_geofence.py index 00866cca..13a935f2 100644 --- a/spp_hazard/tests/test_geofence.py +++ b/spp_hazard/tests/test_geofence.py @@ -2,14 +2,11 @@ """Tests for geofence extensions in spp_hazard.""" import json -import logging from odoo.tests import tagged from .common import HazardTestCase -_logger = logging.getLogger(__name__) - @tagged("post_install", "-at_install") class TestHazardGeofence(HazardTestCase): diff --git a/spp_hazard/tests/test_hazard_category.py b/spp_hazard/tests/test_hazard_category.py index 7a118b5f..45f96a51 100644 --- a/spp_hazard/tests/test_hazard_category.py +++ b/spp_hazard/tests/test_hazard_category.py @@ -1,15 +1,11 @@ # Part of OpenSPP. See LICENSE file for full copyright and licensing details. -import logging - from psycopg2 import IntegrityError from odoo.tests import mute_logger from .common import HazardTestCase -_logger = logging.getLogger(__name__) - class TestHazardCategory(HazardTestCase): """Test cases for spp.hazard.category model.""" diff --git a/spp_hazard/tests/test_hazard_impact.py b/spp_hazard/tests/test_hazard_impact.py index e0e7e28b..f69bce94 100644 --- a/spp_hazard/tests/test_hazard_impact.py +++ b/spp_hazard/tests/test_hazard_impact.py @@ -1,7 +1,5 @@ # Part of OpenSPP. See LICENSE file for full copyright and licensing details. -import logging - from psycopg2 import IntegrityError from odoo.exceptions import ValidationError @@ -9,8 +7,6 @@ from .common import HazardTestCase -_logger = logging.getLogger(__name__) - class TestHazardImpact(HazardTestCase): """Test cases for spp.hazard.impact model.""" diff --git a/spp_hazard/tests/test_hazard_impact_type.py b/spp_hazard/tests/test_hazard_impact_type.py index 5f228c82..8d68e03a 100644 --- a/spp_hazard/tests/test_hazard_impact_type.py +++ b/spp_hazard/tests/test_hazard_impact_type.py @@ -1,15 +1,11 @@ # Part of OpenSPP. See LICENSE file for full copyright and licensing details. -import logging - from psycopg2 import IntegrityError from odoo.tests import mute_logger from .common import HazardTestCase -_logger = logging.getLogger(__name__) - class TestHazardImpactType(HazardTestCase): """Test cases for spp.hazard.impact.type model.""" diff --git a/spp_hazard/tests/test_hazard_incident.py b/spp_hazard/tests/test_hazard_incident.py index ea42c208..c92f4e43 100644 --- a/spp_hazard/tests/test_hazard_incident.py +++ b/spp_hazard/tests/test_hazard_incident.py @@ -1,16 +1,13 @@ # Part of OpenSPP. See LICENSE file for full copyright and licensing details. -import logging - from psycopg2 import IntegrityError +from odoo import Command from odoo.exceptions import ValidationError from odoo.tests import mute_logger from .common import HazardTestCase -_logger = logging.getLogger(__name__) - class TestHazardIncident(HazardTestCase): """Test cases for spp.hazard.incident model.""" @@ -99,7 +96,7 @@ def test_06_area_linking(self): # Link area self.incident.write( { - "area_ids": [(4, self.area.id)], + "area_ids": [Command.link(self.area.id)], } ) self.assertEqual(self.incident.area_count, 1) @@ -124,7 +121,7 @@ def test_08_identify_potentially_affected(self): # Link area to incident self.incident.write( { - "area_ids": [(4, self.area.id)], + "area_ids": [Command.link(self.area.id)], } ) @@ -156,3 +153,190 @@ def test_10_action_view_impacts(self): self.assertEqual(action["type"], "ir.actions.act_window") self.assertEqual(action["res_model"], "spp.hazard.impact") self.assertEqual(action["domain"], [("incident_id", "=", self.incident.id)]) + + def test_11_action_view_areas(self): + """Test the action to view areas.""" + self.incident.write({"area_ids": [Command.link(self.area.id)]}) + action = self.incident.action_view_areas() + self.assertEqual(action["type"], "ir.actions.act_window") + self.assertEqual(action["res_model"], "spp.area") + self.assertEqual(action["domain"], [("id", "in", self.incident.area_ids.ids)]) + + def test_12_incident_area_display_name(self): + """Test _compute_display_name on HazardIncidentArea.""" + incident_area = self.env["spp.hazard.incident.area"].create( + { + "incident_id": self.incident.id, + "area_id": self.area.id, + } + ) + expected = f"{self.incident.name} - {self.area.name}" + self.assertEqual(incident_area.display_name, expected) + + def test_13_incident_area_unique_constraint(self): + """Test duplicate (incident, area) raises IntegrityError.""" + self.env["spp.hazard.incident.area"].create( + { + "incident_id": self.incident.id, + "area_id": self.area.id, + } + ) + with self.assertRaises(IntegrityError), mute_logger("odoo.sql_db"): + self.env["spp.hazard.incident.area"].create( + { + "incident_id": self.incident.id, + "area_id": self.area.id, + } + ) + + def test_14_identify_no_areas(self): + """Test identify returns empty when no areas linked.""" + affected = self.incident.identify_potentially_affected_registrants() + self.assertFalse(affected) + + def test_15_close_sets_end_date(self): + """Test closing without end_date auto-sets today.""" + incident = self.env["spp.hazard.incident"].create( + { + "name": "Close Date Test", + "code": "CLOSE-DATE-TEST", + "category_id": self.category_typhoon.id, + "start_date": "2024-01-01", + } + ) + self.assertFalse(incident.end_date) + incident.action_close() + self.assertTrue(incident.end_date) + self.assertEqual(incident.status, "closed") + + def test_16_close_preserves_existing_end_date(self): + """Test closing with existing end_date does not overwrite it.""" + incident = self.env["spp.hazard.incident"].create( + { + "name": "Preserve End Date Test", + "code": "PRESERVE-END-TEST", + "category_id": self.category_typhoon.id, + "start_date": "2024-01-01", + "end_date": "2024-02-01", + } + ) + incident.action_close() + self.assertEqual(str(incident.end_date), "2024-02-01") + self.assertEqual(incident.status, "closed") + + def test_17_is_ongoing_alert_and_recovery(self): + """Test is_ongoing for alert and recovery statuses.""" + incident = self.env["spp.hazard.incident"].create( + { + "name": "Alert Ongoing Test", + "code": "ALERT-ONGOING", + "category_id": self.category_typhoon.id, + "start_date": "2024-01-01", + "status": "alert", + } + ) + # Alert with no end_date is ongoing + self.assertTrue(incident.is_ongoing) + + # Recovery with no end_date is ongoing + incident.write({"status": "recovery"}) + self.assertTrue(incident.is_ongoing) + + # Closed with no end_date is NOT ongoing + incident.write({"status": "closed"}) + self.assertFalse(incident.is_ongoing) + + def test_18_date_validation_on_update(self): + """Test date constraint fires on update, not just creation.""" + incident = self.env["spp.hazard.incident"].create( + { + "name": "Update Date Test", + "code": "UPDATE-DATE-TEST", + "category_id": self.category_typhoon.id, + "start_date": "2024-01-15", + } + ) + with self.assertRaises(ValidationError): + incident.write({"end_date": "2024-01-01"}) + + def test_19_affected_registrant_count_distinct(self): + """Test affected_registrant_count counts distinct registrants.""" + registrant2 = self.env["res.partner"].create( + { + "name": "Second Registrant", + "is_registrant": True, + "is_group": False, + } + ) + incident = self.env["spp.hazard.incident"].create( + { + "name": "Multi Registrant Test", + "code": "MULTI-REG-TEST", + "category_id": self.category_typhoon.id, + "start_date": "2024-01-01", + } + ) + Impact = self.env["spp.hazard.impact"] + # Two impacts for same registrant, one for different + Impact.create( + { + "incident_id": incident.id, + "registrant_id": self.registrant.id, + "impact_type_id": self.impact_type_displacement.id, + "damage_level": "moderate", + "impact_date": "2024-01-02", + } + ) + Impact.create( + { + "incident_id": incident.id, + "registrant_id": self.registrant.id, + "impact_type_id": self.impact_type_property.id, + "damage_level": "severe", + "impact_date": "2024-01-02", + } + ) + Impact.create( + { + "incident_id": incident.id, + "registrant_id": registrant2.id, + "impact_type_id": self.impact_type_displacement.id, + "damage_level": "minimal", + "impact_date": "2024-01-02", + } + ) + # 3 impacts but only 2 distinct registrants + self.assertEqual(incident.impact_count, 3) + self.assertEqual(incident.affected_registrant_count, 2) + + def test_20_affected_registrant_count_empty(self): + """Test affected_registrant_count on new/empty recordset.""" + empty = self.env["spp.hazard.incident"].browse() + empty._compute_affected_registrant_count() + # Should not raise; field set to 0 + + def test_21_multi_record_close(self): + """Test action_close works on multiple records.""" + inc1 = self.env["spp.hazard.incident"].create( + { + "name": "Multi Close 1", + "code": "MULTI-CLOSE-1", + "category_id": self.category_typhoon.id, + "start_date": "2024-01-01", + } + ) + inc2 = self.env["spp.hazard.incident"].create( + { + "name": "Multi Close 2", + "code": "MULTI-CLOSE-2", + "category_id": self.category_typhoon.id, + "start_date": "2024-03-01", + "end_date": "2024-04-01", + } + ) + (inc1 | inc2).action_close() + self.assertEqual(inc1.status, "closed") + self.assertEqual(inc2.status, "closed") + # inc1 gets auto end_date, inc2 preserves its own + self.assertTrue(inc1.end_date) + self.assertEqual(str(inc2.end_date), "2024-04-01") diff --git a/spp_hazard/tests/test_registrant.py b/spp_hazard/tests/test_registrant.py new file mode 100644 index 00000000..e343295b --- /dev/null +++ b/spp_hazard/tests/test_registrant.py @@ -0,0 +1,238 @@ +# Part of OpenSPP. See LICENSE file for full copyright and licensing details. + +from odoo.exceptions import AccessError + +from .common import HazardTestCase + + +class TestRegistrant(HazardTestCase): + """Test cases for res.partner hazard extension.""" + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.incident = cls.env["spp.hazard.incident"].create( + { + "name": "Registrant Test Incident", + "code": "REG-TEST-INC-001", + "category_id": cls.category_typhoon.id, + "start_date": "2024-01-01", + "severity": "3", + } + ) + cls.impact = cls.env["spp.hazard.impact"].create( + { + "incident_id": cls.incident.id, + "registrant_id": cls.registrant.id, + "impact_type_id": cls.impact_type_displacement.id, + "damage_level": "moderate", + "impact_date": "2024-01-02", + } + ) + + def test_hazard_impact_count(self): + """Test that impact count updates when impacts are created/deleted.""" + self.assertEqual(self.registrant.hazard_impact_count, 1) + + # Create another impact + impact2 = self.env["spp.hazard.impact"].create( + { + "incident_id": self.incident.id, + "registrant_id": self.registrant.id, + "impact_type_id": self.impact_type_property.id, + "damage_level": "severe", + "impact_date": "2024-01-02", + } + ) + self.assertEqual(self.registrant.hazard_impact_count, 2) + + # Delete impact + impact2.unlink() + self.assertEqual(self.registrant.hazard_impact_count, 1) + + def test_has_active_impact_with_active_incident(self): + """Test flag is True when incident is active.""" + self.assertEqual(self.incident.status, "active") + self.assertTrue(self.registrant.has_active_impact) + + def test_has_active_impact_with_closed_incident(self): + """Test flag is False when incident is closed.""" + self.incident.write({"status": "closed", "end_date": "2024-01-15"}) + self.assertFalse(self.registrant.has_active_impact) + + def test_has_active_impact_transitions(self): + """Test flag updates when incident status changes.""" + self.incident.write({"status": "active", "end_date": False}) + self.assertTrue(self.registrant.has_active_impact) + + # Move to recovery - should still be active + self.incident.action_set_recovery() + self.assertTrue(self.registrant.has_active_impact) + + # Close - should no longer be active + self.incident.action_close() + self.assertFalse(self.registrant.has_active_impact) + + def test_action_view_hazard_impacts(self): + """Test the action returns correct domain and model.""" + action = self.registrant.action_view_hazard_impacts() + self.assertEqual(action["type"], "ir.actions.act_window") + self.assertEqual(action["res_model"], "spp.hazard.impact") + self.assertEqual(action["domain"], [("registrant_id", "=", self.registrant.id)]) + + def test_get_impact_history(self): + """Test impact history returns impacts sorted by date desc.""" + # Create a second impact with an earlier date + self.env["spp.hazard.impact"].create( + { + "incident_id": self.incident.id, + "registrant_id": self.registrant.id, + "impact_type_id": self.impact_type_property.id, + "damage_level": "minimal", + "impact_date": "2024-01-01", + } + ) + history = self.registrant.get_impact_history() + self.assertEqual(len(history), 2) + # Most recent date first + self.assertGreaterEqual(history[0].impact_date, history[1].impact_date) + + def test_get_active_incident_impacts(self): + """Test filtering to only active/alert/recovery incidents.""" + self.incident.write({"status": "active", "end_date": False}) + active_impacts = self.registrant.get_active_incident_impacts() + self.assertEqual(len(active_impacts), 1) + + # Close the incident + self.incident.write({"status": "closed", "end_date": "2024-01-15"}) + active_impacts = self.registrant.get_active_incident_impacts() + self.assertEqual(len(active_impacts), 0) + + def test_no_impacts(self): + """Test fresh registrant has no impacts.""" + fresh = self.env["res.partner"].create( + { + "name": "Fresh Registrant", + "is_registrant": True, + "is_group": False, + } + ) + self.assertEqual(fresh.hazard_impact_count, 0) + self.assertFalse(fresh.has_active_impact) + self.assertEqual(len(fresh.get_impact_history()), 0) + self.assertEqual(len(fresh.get_active_incident_impacts()), 0) + + def test_group_registrant_hazard_fields(self): + """Test that group registrants also support hazard fields.""" + group = self.env["res.partner"].create( + { + "name": "Test Group", + "is_registrant": True, + "is_group": True, + } + ) + self.assertEqual(group.hazard_impact_count, 0) + self.assertFalse(group.has_active_impact) + + +class TestRegistrantSecurity(HazardTestCase): + """Test security access for hazard impacts on registrants.""" + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.incident = cls.env["spp.hazard.incident"].create( + { + "name": "Security Test Incident", + "code": "SEC-TEST-INC-001", + "category_id": cls.category_typhoon.id, + "start_date": "2024-01-01", + } + ) + + def test_viewer_can_read_impacts(self): + """Test that viewers can read impact records (validates 1.3 fix).""" + impact = self.env["spp.hazard.impact"].create( + { + "incident_id": self.incident.id, + "registrant_id": self.registrant.id, + "impact_type_id": self.impact_type_displacement.id, + "damage_level": "moderate", + "impact_date": "2024-01-02", + } + ) + # Viewer should be able to read + impact.with_user(self.hazard_viewer).read(["damage_level"]) + + def test_viewer_cannot_create_incidents(self): + """Test that viewers cannot create incidents.""" + with self.assertRaises(AccessError): + self.env["spp.hazard.incident"].with_user(self.hazard_viewer).create( + { + "name": "Viewer Incident", + "code": "VIEWER-INC", + "category_id": self.category_typhoon.id, + "start_date": "2024-01-01", + } + ) + + def test_officer_can_create_incidents(self): + """Test that officers can create incidents.""" + incident = ( + self.env["spp.hazard.incident"] + .with_user(self.hazard_officer) + .create( + { + "name": "Officer Incident", + "code": "OFFICER-INC", + "category_id": self.category_typhoon.id, + "start_date": "2024-01-01", + } + ) + ) + self.assertTrue(incident) + + def test_officer_cannot_delete_incidents(self): + """Test that officers cannot delete incidents.""" + incident = self.env["spp.hazard.incident"].create( + { + "name": "Delete Test", + "code": "DEL-TEST-INC", + "category_id": self.category_typhoon.id, + "start_date": "2024-01-01", + } + ) + with self.assertRaises(AccessError): + incident.with_user(self.hazard_officer).unlink() + + def test_officer_cannot_write_categories(self): + """Test that officers cannot modify categories (validates 1.4 fix).""" + with self.assertRaises(AccessError): + self.category_typhoon.with_user(self.hazard_officer).write({"name": "Hacked"}) + + def test_officer_cannot_create_categories(self): + """Test that officers cannot create categories (validates 1.4 fix).""" + with self.assertRaises(AccessError): + self.env["spp.hazard.category"].with_user(self.hazard_officer).create( + { + "name": "Unauthorized", + "code": "UNAUTH_CAT", + } + ) + + def test_manager_has_full_access(self): + """Test that managers can create and delete records.""" + incident = ( + self.env["spp.hazard.incident"] + .with_user(self.hazard_manager) + .create( + { + "name": "Manager Incident", + "code": "MGR-INC", + "category_id": self.category_typhoon.id, + "start_date": "2024-01-01", + } + ) + ) + self.assertTrue(incident) + incident.with_user(self.hazard_manager).unlink() diff --git a/spp_hazard/views/hazard_category_views.xml b/spp_hazard/views/hazard_category_views.xml index 2ae034ff..52d60c51 100644 --- a/spp_hazard/views/hazard_category_views.xml +++ b/spp_hazard/views/hazard_category_views.xml @@ -10,7 +10,7 @@ - + @@ -22,21 +22,6 @@
    -
    - -
    + - - - - - - - - - - - - - - + + + + + + + + + + +
    @@ -94,6 +77,7 @@ + - + + + diff --git a/spp_hazard/views/hazard_impact_type_views.xml b/spp_hazard/views/hazard_impact_type_views.xml index 34ff42e2..befbeae3 100644 --- a/spp_hazard/views/hazard_impact_type_views.xml +++ b/spp_hazard/views/hazard_impact_type_views.xml @@ -11,7 +11,7 @@ - + @@ -39,21 +39,17 @@ - - - - - - + + @@ -67,6 +63,7 @@ + - + - +
    +
    + + Awaiting Verification - Review and verify this impact record. +
    +
    + + Disputed - This impact record has been disputed and needs investigation. +
    + + - - - - - + + + @@ -146,6 +174,7 @@ + - + Impact Records ir.actions.act_window spp.hazard.impact - list,form + list,kanban,form hazard-impacts {} diff --git a/spp_hazard/views/hazard_incident_views.xml b/spp_hazard/views/hazard_incident_views.xml index 4851d898..687dbde6 100644 --- a/spp_hazard/views/hazard_incident_views.xml +++ b/spp_hazard/views/hazard_incident_views.xml @@ -7,6 +7,7 @@ @@ -23,7 +24,13 @@ decoration-warning="status == 'recovery'" decoration-muted="status == 'closed'" /> - + @@ -65,6 +72,13 @@ /> + +
    -
    +
    + + Alert Phase - This incident is in alert status. Activate when response begins. +
    +
    + + Recovery Phase - This incident is in recovery. Close when recovery is complete. +
    - + - + + - + Hazard Incidents ir.actions.act_window spp.hazard.incident - list,form + list,kanban,form hazard-incidents - {} + {'search_default_alert': 1, 'search_default_active': 1, 'search_default_recovery': 1}

    Record your first hazard incident diff --git a/spp_hazard/views/registrant_views.xml b/spp_hazard/views/registrant_views.xml index 1b1ec29b..bf42a0a8 100644 --- a/spp_hazard/views/registrant_views.xml +++ b/spp_hazard/views/registrant_views.xml @@ -1,6 +1,8 @@ + view.individual.form.hazard res.partner @@ -35,7 +37,13 @@ role="alert" invisible="not has_active_impact" > - Active incident impacts recorded + + Active incident impacts recorded. + This registrant is affected by one or more ongoing hazard incidents.

    No emergency response records yet. Impact records will appear here when this registrant is affected by a hazard incident. @@ -54,7 +62,13 @@ - + - +