Skip to content

feat(spp_drims): Incident Management review — states, closed-incident limits, dashboard and warehouses - #416

Merged
emjay0921 merged 17 commits into
19.0from
feat/1100-drims-incident-management
Aug 20, 2026
Merged

feat(spp_drims): Incident Management review — states, closed-incident limits, dashboard and warehouses#416
emjay0921 merged 17 commits into
19.0from
feat/1100-drims-incident-management

Conversation

@emjay0921

@emjay0921 emjay0921 commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Why is this change needed?

OP#1100 (DRIMS - Full review - Incident Management) and all of its children have passed QA — this raises the PR for that work, per the flow of opening PRs once QA signs off.

Ticket Status
OP#1094 Invalidate stock KPI cache when warehouses change Closed
OP#1123 Dashboard KPI boxes navigate unexpectedly Test pass
OP#1157 Enable "Alert" state for incidents Test pass
OP#1158 Limit possible actions on incidents in state "Closed" Test pass
OP#1159 Hide "Impact" tab on Incidents Test pass
OP#1160 Changes to Incident Dashboard view Test pass
OP#1164 Add warehouses to an incident Test pass

What is in here

  • Incident lifecycle (OP#1157) — an incident is now entered as a Draft and classified from there, with Flag As Alert and Set Active offered side by side. The lifecycle is Draft → Alert or Active → Recovery → Closed, and Alert stays reachable later. Draft was added to the list's default filter, or a newly recorded incident would have disappeared from the list it was created in.
  • Closed incidents are inert (OP#1158) — submitting, approving and allocating requests, accepting donations, deploying personnel and editing incident fields are all refused once an incident is closed, and the Hazard Category no longer links out.
  • Incident warehouses (OP#1164) — warehouses and incidents are linked bidirectionally, and the donation and request pickers filter to the incident's warehouses.
  • Dashboard (OP#1160, OP#1123, OP#1094) — Units/Products counts incident stock net of allocations and Distributed is net of returns; clicking a KPI box no longer navigates away, only the incident name opens the form; and the stock-value KPI refreshes when a warehouse's incidents change.
  • Impact tab hidden (OP#1159) pending OP#1155.

Closed incidents: what is enforced where

Enforced on the server: the DRIMS operations (_drims_ensure_open), and the incident's own lifecycle — a draft cannot be closed, and a closed incident cannot be moved back to alert, active or recovery.

UI only, deliberately: editing the incident's own fields while closed. A write() guard would have to allowlist its way around the stored KPI computes, which legitimately write to closed incidents — this PR's own migration recomputes them, and so would any future repair. Blocking writes there would break the mechanism that keeps a closed incident's numbers honest, to prevent an edit that carries no operational consequence. The data that matters is protected by the operation guards.

Version bumps: spp_drims to 19.0.3.1.0 and spp_hazard to 19.0.2.1.0, with migrations/19.0.3.1.0/post-migration.py. Three stored computes changed meaning rather than being added, so an upgrade alone would leave existing rows on the old semantics — indefinitely for closed incidents, which the refresh cron skips. The migration purges the stale spp.data.value cache rows first (the computes prefer a live cache entry) and then recomputes all three for every incident.

Unit tests executed by the author

On the merged branch, across the base module and everything downstream of it:

spp_hazard             0 failed, 0 error(s) of  71 tests
spp_hazard_programs    0 failed, 0 error(s) of  24 tests
spp_drims              0 failed, 0 error(s) of 284 tests
spp_drims_sl           0 failed, 0 error(s) of   3 tests
spp_drims_sl_demo      0 failed, 0 error(s) of   8 tests

./spp lint --all-files clean.

How to test manually

  1. Create an incident — it saves as Draft and offers exactly Flag As Alert and Set Active. Confirm it still appears in the incident list.
  2. Classify it either way, then walk Active → Recovery → Closed.
  3. On a closed incident: its fields are read-only, Hazard Category is plain text rather than a link, and a request or donation against it is refused.
  4. Link a warehouse to an incident and confirm the donation and request pickers narrow to it.
  5. On the dashboard, click a KPI box — it must not navigate; only the incident name opens the form.

Related links

https://openspp.openproject.com/work_packages/1100

Reviewer notes

OP#1157 changed requirement twice, which is why its history on this branch is three commits. Round 1 exposed the existing Alert state via a button; round 2 made Alert the entry state; round 3 replaced that with a new Draft state, because QA wanted the person recording an incident to say what it is rather than have Alert assumed. The final shape is round 3.

Two decisions were made where the ticket was silent, both confirmed by QA on the ticket: a draft cannot be closed — a mistaken one is deleted — and a draft does not count as a live incident anywhere (not "ongoing", not marking registrants affected, and not offered where only Active incidents are, such as low-stock alerting and the request-from-template picker). It starts counting once classified.

spp_hazard is a base module with dependants, so the runs above cover both modules that inherit the incident form, not just the module under change.

The branch was brought up to date by merging 19.0, not rebasing, since it was already published.

emjay0921 added 14 commits July 24, 2026 12:13
…of allocations) and Distributed net of returns (#1160)
QA round 1 on OP#1157 returned three findings and a question.

Flag As Alert was inserted before the statusbar, which rendered it last.
It is now anchored on the first header button so the order reads Flag As
Alert, Start Recovery, Close Incident — the order the workflow runs in.
Anchored by button name rather than position, so a reordering of the base
view fails loudly at upgrade instead of silently drifting back.

New incidents landed straight in Active, skipping the triage step the
Alert state exists for. The status default becomes "alert", making the
lifecycle Alert -> Active -> Recovery -> Closed. Changed on spp_hazard,
where the state machine lives, so every consumer behaves the same rather
than the same model behaving differently depending on which modules are
installed.

Close Incident was already correct in effect — with four states,
"not closed" and "alert, active or recovery" are the same set — but it is
now stated positively so it stays right if a state is ever added.

QA also asked when Recovery can be set: only from Active, since Start
Recovery is hidden otherwise. With Alert as the entry state a new incident
is confirmed Active before Recovery is offered. A test pins that so the
answer does not quietly change.

Four spp_hazard tests asserted the old default; they now confirm Active
explicitly rather than assuming it.

Not changed, and flagged for QA instead: DRIMS low-stock alerting and the
request-from-template picker both filter on status = "active", so an
incident sitting in Alert reaches neither until it is confirmed. Whether an
alert-state incident should drive stock alerting is a product decision.

Verified across every spp_hazard dependent — spp_hazard, spp_drims,
spp_hazard_programs, spp_api_v2_gis, spp_gis_indicators, spp_drims_sl_demo
— and by hand on a fresh database.

OP#1157
Closing an incident left Hazard Category clickable, so a closed record
still offered a way through to the category form. Making the field
readonly does not cover this — a readonly many2one still renders as an
internal link — and no_open cannot be made conditional, because options is
parsed as a static dict and cannot reference status.

Declare the field twice instead, with mutually exclusive invisible: an open
incident keeps its link, a closed one is inert text. The same pattern is
already used across the codebase wherever a field has to change shape with
state.

Asserted on the arch rather than through behaviour, since whether a
many2one is clickable is decided in the client, not the ORM.
…ent-management

# Conflicts:
#	spp_drims/models/request.py
#	spp_drims/views/request_views.xml
OP#1079 made the request line's quantity_allocated a stored compute over
per-warehouse allocation rows, so the helper's direct write no longer
registered and the Units KPI counted stock that had in fact been allocated.
Round 2 made Alert the entry state. QA has since changed the requirement: an
incident should be entered as a draft, and the person recording it says what
it is — Flag As Alert for something being watched, Set Active for a response
already under way. Neither is assumed on their behalf.

Adds a draft state at the head of the selection and makes it the default.
Set Active is now offered from Draft as well, so a response already under way
does not have to be flagged as an alert first. Start Recovery and Close
Incident stay hidden there, which is what leaves a draft showing exactly the
two buttons asked for.

The list's default filter needed Draft adding too. It pre-selects Alert,
Active and Recovery, so with Draft as the entry state a newly created
incident would have disappeared from the list it was created in and read as
having failed to save. A test asserts both the filter and its default,
because nothing at the model level would notice.

A draft is deliberately not a live incident: it is excluded from is_ongoing,
from the affected-registrant check, and from the Active-only consumers. It
starts counting once classified. A draft also cannot be closed — a mistaken
one is deleted rather than closed.

Six tests asserting the round-2 lifecycle are rewritten, not dropped.
@codecov

codecov Bot commented Aug 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.24771% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.93%. Comparing base (a4ffdf9) to head (2606c83).

Files with missing lines Patch % Lines
spp_hazard/models/hazard_incident.py 80.00% 2 Missing ⚠️
spp_drims/models/hazard_incident.py 98.21% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             19.0     #416      +/-   ##
==========================================
+ Coverage   72.33%   72.93%   +0.59%     
==========================================
  Files         419      464      +45     
  Lines       29902    31664    +1762     
==========================================
+ Hits        21630    23094    +1464     
- Misses       8272     8570     +298     
Flag Coverage Δ
spp_api_v2_change_request 66.53% <ø> (ø)
spp_api_v2_cycles 71.03% <ø> (ø)
spp_api_v2_entitlements 70.23% <ø> (ø)
spp_api_v2_gis 71.57% <ø> (ø)
spp_api_v2_programs 92.22% <ø> (ø)
spp_api_v2_simulation 71.19% <ø> (?)
spp_base_common 91.07% <ø> (ø)
spp_case_entitlements 100.00% <ø> (?)
spp_case_programs 100.00% <ø> (?)
spp_change_request_v2 77.83% <ø> (ø)
spp_cr_type_assign_program 92.07% <ø> (ø)
spp_dci_compliance 93.01% <ø> (?)
spp_drims 84.06% <98.98%> (+1.07%) ⬆️
spp_hazard 99.20% <80.00%> (?)
spp_programs 65.27% <ø> (ø)
spp_registry 87.22% <ø> (+0.07%) ⬆️
spp_security 69.56% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
spp_drims/models/donation.py 97.00% <100.00%> (+0.09%) ⬆️
spp_drims/models/personnel.py 98.41% <100.00%> (+0.16%) ⬆️
spp_drims/models/request.py 94.01% <100.00%> (+0.13%) ⬆️
spp_drims/models/res_config_settings.py 63.79% <100.00%> (+2.68%) ⬆️
spp_drims/models/returns.py 93.06% <ø> (ø)
spp_drims/models/stock_warehouse.py 77.94% <100.00%> (+7.35%) ⬆️
spp_drims/models/hazard_incident.py 76.27% <98.21%> (+10.99%) ⬆️
spp_hazard/models/hazard_incident.py 98.05% <80.00%> (ø)

... and 45 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@emjay0921
emjay0921 marked this pull request as ready for review August 14, 2026 03:36

@gonzalesedwin1123 gonzalesedwin1123 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — this is a well-built PR. The tests are genuinely good (arch-level assertions with rationale, both contributing modules covered, guards tested positive and negative), and I verified the OP#1123 kanban approach against the Odoo 19 source: can_open="0" on <kanban> plus <a type="open"> is correct — type="open" calls openRecord unconditionally (kanban_record.js:346), independent of canOpenRecords. Routing _inverse_drims_warehouses through stock.warehouse.write so the OP#1094 refresh hook fires is a nice touch.

Two things need changes before this can land, plus some questions and nits.

1. Blocker — no version bump and no migration on either module

spp_drims stays at 19.0.3.0.0 and spp_hazard at 19.0.2.0.2, identical to 19.0. Both modules shipped in release 2026.08, so the released-baseline rule applies (same-version code changes give upgraders no signal and skip migrations/ scripts).

On an existing database without an upgrade:

  • The new ir.config_parameter record drims.warehouse.filter_by_incident never loads (the code default masks this one).
  • None of the view changes load — Draft filter/statusbar, Flag As Alert, closed-incident readonly, kanban can_open — i.e. the bulk of the user-visible work.

And even with an upgrade, stored computes whose semantics changed are not recomputed — Odoo only auto-computes stored fields when the column is newly created:

  • drims_total_stock_units / drims_stock_item_count changed meaning from "physical warehouse contents" to "incident-related stock net of allocations". Existing rows keep old-semantics numbers until a dependency happens to change — for quiet or closed incidents, indefinitely.
  • drims_distributed_value is now net of returns. Stored values and live spp.data.value cache rows still hold gross numbers. The 15-minute cron self-heals open incidents, but closed incidents are excluded from the cron and never recompute.

Ask: bump both manifests and add a migrations/<version>/post-migration.py that purges the drims_distributed_value (and arguably drims_stock_value) cache rows and recomputes the three stored KPI fields for all incidents.

2. Major — "closed is inert" is UI-only for the incident itself; lifecycle transitions are unguarded server-side

The DRIMS operations got real server-side guards (_drims_ensure_open), but the incident's own lifecycle didn't:

  • action_close, action_set_active, and the new action_set_alert have no state guards — visibility is only enforced in the form header. Via RPC/import/shell, a draft can be closed (contradicting the QA-confirmed "a draft cannot be closed — a mistaken one is deleted") and a closed incident can be flipped back to active/alert, sidestepping every OP#1158 guard.
  • Field edits on a closed incident are only readonly= in the views, which Odoo does not enforce on write — so "editing incident fields are all refused" holds only in the web client.

Ask: at minimum, raise in the action_* methods when the transition isn't legal from the current state (e.g. action_close refuses drafts, action_set_alert/action_set_active refuse closed). Optionally a write() guard on closed incidents (allowlisting status/end_date/chatter fields) if server-side enforcement of field edits is wanted. If UI-only is the accepted posture for field edits, let's state that explicitly in the PR description instead.

3. Questions — gaps in the closed-incident guard coverage (possibly intentional)

  • action_create_dispatch is not guarded: a request allocated before closure can still be dispatched afterwards — stock leaves the warehouse for a closed incident. Allocate is guarded; dispatch isn't. Intentional?
  • action_mark_received / action_stock on donations are not guarded: a donation pledged before closure can be received and stocked in afterwards. Deliberate (goods in flight), or a gap?
  • The create-time guards check vals only, so write({'incident_id': <closed>}) re-points an existing donation/personnel record to a closed incident unguarded.
  • A request can still be created against a closed incident (only submit/approve/allocate raise) while a donation cannot even be created — the guard tests themselves rely on creating requests against a closed incident. Is the asymmetry intended?

4. Nits

  • spp_hazard/tests/test_registrant.py — comment says "incidents now start in alert"; they start in draft.
  • _cron_refresh_drims_kpis — the "status field values: alert, active, recovery, closed" comment is missing draft.
  • _compute_allowed_warehouse_ids is duplicated verbatim (docstring included) in donation.py and request.py; a small shared helper would keep the filter-on/fallback policy in one place.
  • Units/Distributed asymmetry: a confirmed return reduces Distributed, but the returned goods are not added back to incident Units (their allocation still counts them out; re-allocating them would count them out twice, clamped at 0 per product). Probably fine — deserves a note.
  • In base spp_hazard without DRIMS installed, a draft has no UI path to Alert (the button lives in spp_drims) — only Set Active. Fine if Alert is a DRIMS-driven concept, but the state lives in the base module.

Both warehouse-choice tests create a donation with no items. The donations
review on fix/1076-drims-donations-review adds _check_has_lines, which forbids
exactly that — so whichever of the two branches merges second turns 19.0 red,
and with no textual conflict between them nothing warns either author first.

The line is incidental to what these tests assert; it is there to keep the
donation valid.

Not done here: dropping this branch's create-time closed-incident guard in
favour of that review's _check_incident_not_closed constraint, which is
strictly broader because it also catches an existing donation re-pointed at a
closed incident. The constraint does not exist on this branch, so removing the
guard now would leave test_1158_donation_blocked_when_closed with nothing to
raise. It should follow once the donations PR has merged.
… and lifecycle guards

Version bumps on spp_drims and spp_hazard, and a migration for the part a bump
alone does not fix. Three stored computes changed meaning rather than being
added: incident stock units and item count now count incident-related stock net
of allocations, and distributed value is net of confirmed returns. Odoo only
computes a stored field for existing rows when its column is new, so an upgraded
database would keep the old numbers until some dependency happened to change —
and for closed incidents never, since the refresh cron skips them. The migration
drops the stale spp.data.value cache rows the computes would otherwise prefer,
then recomputes all three for every incident, closed ones included.

The incident's own lifecycle is now enforced on the server, not only hidden in
the form header. A draft cannot be closed — QA's rule is that a mistakenly
entered incident is deleted — and a closed incident cannot be moved back to
alert, active or recovery, which over RPC would have sidestepped every guard
that keys off `closed`. Alert goes through the same gate.

Field edits on a closed incident stay a UI-level rule, deliberately: a write()
guard would have to allowlist its way around the stored KPI computes, which
legitimately write to closed incidents. The rule that protects data is on the
DRIMS operations, and those are already guarded server-side.

Three spp_hazard tests closed an incident straight after creating it, which the
draft entry state makes illegal; they now take it through the lifecycle first.

Nits from the review: _compute_allowed_warehouse_ids was duplicated verbatim in
donation.py and request.py and now calls one helper on the incident, the cron
comment listing status values was missing draft, and a test comment still said
incidents start in alert.
@emjay0921

Copy link
Copy Markdown
Contributor Author

Thanks — blockers 1 and 2 are done, the nits are done, and the questions in 3 are answered below with one of them fixed. Pushed as f5592290 (plus c433ad82 earlier for the #414 fixture collision).

1. Version bump + migration — done. spp_drims 19.0.3.0.019.0.3.1.0, spp_hazard 19.0.2.0.219.0.2.1.0, both with changelog entries, and spp_drims/migrations/19.0.3.1.0/post-migration.py.

The migration does the two things you identified, and the second is the one I would have missed: it drops the drims_distributed_value and drims_stock_value cache rows first — because the compute prefers a live cache entry, so recomputing without purging would have read the pre-change numbers straight back — then recomputes drims_total_stock_units, drims_stock_item_count and drims_distributed_value for every incident via add_to_compute. Deliberately not filtered to open incidents: closed ones are exactly where a stale number would sit forever, since the cron skips them.

2. Server-side lifecycle guards — done. _ensure_status_change_allowed on spp.hazard.incident, called by action_close, action_set_active, action_set_recovery and (from spp_drims) action_set_alert. Two rules, both from the lifecycle documented on status: a draft is not closed, and nothing moves out of closed.

On the optional write() guard, I went with your "state the posture explicitly" branch, and there is a concrete reason rather than just cost: a blanket guard on closed incidents would have to allowlist its way around the stored KPI computes, which legitimately write to closed incidents — the migration above does exactly that, and so would any future recompute. Blocking writes there would break the mechanism that keeps closed incidents' numbers honest. So field edits stay a UI rule and the enforcement lives on the DRIMS operations, which have real guards. Now said in the PR body too.

Three spp_hazard tests closed an incident immediately after creating it, which the draft entry state makes illegal — they now go through the lifecycle first. That is the guard working, but worth flagging as a behaviour change for anyone with their own fixtures.

3. Your questions:

4. Nits — all done except the two that are notes rather than changes:

  • _compute_allowed_warehouse_ids now calls one _drims_allowed_warehouses helper on the incident. It is written to tolerate an empty incident and to run the warehouse search only on the fallback path, so a list view computing this field costs no extra query per row in the common case.
  • test_registrant.py comment fixed (draft, not alert); the cron's status list now includes draft.
  • Units/Distributed asymmetry: agreed it is fine, and I would rather record it in the ticket than in a code comment that will drift — noted on OP#1100.
  • Alert having no UI path in base spp_hazard: intended. Alert is a DRIMS concept; the state lives in the base module because the base state machine owns status, but the button belongs with the module that uses it.

spp_drims + spp_hazard suites: 359 tests, 0 failed on this branch. pre-commit clean, READMEs regenerated.

Merge order: this branch and #414 both touch spp_drims's manifest and changelog, so whichever goes second will conflict there — keep the higher version and concatenate the entries. #414 also carries the constraint that two of the deferred items above depend on, so I would land it first.

@gonzalesedwin1123 gonzalesedwin1123 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — both blockers are done and verified, the questions are answered credibly, and the fix quality matches the rest of this batch.

What I re-verified on f5592290 / c433ad82:

  • The migration is correct in the way that matters. It expires the drims_distributed_value / drims_stock_value cache rows before recomputing — checked against spp.data.value.read_values, which excludes expired rows, so the recompute cannot read the pre-change numbers straight back — then add_to_compute over all incidents including closed ones, which is exactly where a stale figure would otherwise sit forever. 19.0.3.1.0 matches the migration directory; spp_hazard's bump needs no migration of its own. Changelogs regenerated on both modules.
  • The lifecycle gate covers all four actions (action_close, action_set_active, action_set_recovery, and spp_drims's action_set_alert), with the four new tests pinning reopen-refused, re-close-refused, draft-deleted-not-closed, and the open lifecycle intact.
  • The write() posture is accepted, and for a better reason than cost: the stored KPI computes legitimately write to closed incidents — this PR's own migration does — so a blanket guard would break the mechanism that keeps closed incidents' numbers honest. Now stated in the PR body, which is what I asked for.
  • The #414 fixture collision is fixed on this side (c433ad82), with the right call on not yet dropping the duplicate donation guard — removing it before #414's constraint exists on this branch would leave test_1158_donation_blocked_when_closed asserting nothing.

On the deferred items — dispatch-after-closure guard, personnel incident_id re-pointing, and the request/donation creation asymmetry — the reasoning for deferring each to the #414/#433/#390 reconciliation is sound, and they're now tracked as a ticket on OpenProject so they survive the queue.

One item that crossed with your push by two minutes: the OP#1160 provenance ruling landed (Units/Products keeps the #1160 definition — your migration already matches it), with one suggestion attached — label the dashboard tiles (or their tooltips) so the provenance-vs-location split between Units/Products and Stock Value is legible. Fine as a follow-up.

Your merge-order input (land #414 first; whichever of the two goes second keeps the higher version and concatenates HISTORY) has been passed to Edwin for the queue decision. Merge itself stays with him per the usual flow.

#433 landed spp_drims 19.0.3.0.4, so the manifest version and the top of the
changelog conflicted. Kept this branch's 19.0.3.1.0 — a minor above the patches
already in — and both changelog entries newest-first. README.rst and the
description page are generated from readme/, so they were rebuilt.
@emjay0921
emjay0921 merged commit bc4bd93 into 19.0 Aug 20, 2026
35 checks passed
@emjay0921
emjay0921 deleted the feat/1100-drims-incident-management branch August 20, 2026 03:08
emjay0921 added a commit that referenced this pull request Aug 20, 2026
#416 landed spp_drims 19.0.3.1.0, so the manifest, the changelog and donation.py
all conflicted. The version stays 19.0.4.0.0 — a major, because this branch
removes a released field — and both changelog sets are kept newest-first.

In donation.py the two branches added different methods at the same point, so
both are kept: this branch's expiry/disposition computes and 19.0's
_compute_allowed_warehouse_ids, which now calls the shared incident helper
introduced by #416. Checked there is only one definition of that compute left.
emjay0921 added a commit that referenced this pull request Aug 20, 2026
With #416 merged, its create-time _drims_ensure_open guard and this branch's
_check_incident_not_closed constraint both applied. The create-time one fired
first, so the constraint was masked and the test asserting it failed.

The constraint is the one to keep, as flagged in both reviews: it is strictly
broader, catching an existing donation re-pointed at a closed incident, which a
create-only check cannot see. OP#1158's test now expects the ValidationError it
raises; the rule it asserts is unchanged.
emjay0921 added a commit that referenced this pull request Aug 20, 2026
… follow-up (#414)

* feat(drims): rework donation creation and lifecycle UX

Adds a Draft start stage to the donation lifecycle
(Draft -> Announced -> Received -> Inspected -> Stocked) with a
"Mark Announced" action, and reshapes the form around it:

- Cancel renamed to "Cancel Donation".
- Donor limited to DRIMS organisations whose role is Donor; donations
  cannot be recorded against a closed incident (domain + constraint).
- At least one item is required to save; the line Description field is
  removed and Pledged must be entered (no default, must be > 0).
- Line columns appear progressively: Received (manual, mandatory) and
  Variance from Announced, Condition and Action from Inspected; Expiry
  Date only when product_expiry is installed.
- Received quantities are entered manually instead of being auto-copied
  from Pledged.
- Once inspected, items with a non-accept disposition are listed in a
  separate "Items Not Accepted for Stock" table.

* fix(drims): prevent adding donation items after draft (#1055)

Donation items can only be added or removed while the donation is in
draft. The line table is read-only from the received state onward so the
"Add a line" row no longer appears once a donation has been received,
and donation-line create/unlink are guarded at the model level. The
inspection wizard's split rows opt out of the guard so splitting an
inspected donation still works.

* fix(drims): make inline-created donation products storable (#1108)

A product added through "Add a line" on a donation was created
non-storable, so it never tracked in inventory and did not appear in
Stock on Hand. The donation-line product field now defaults inline
product creation to a storable Good (type consu, Track Inventory on).

* feat(drims): track follow-up for non-accepted donation items (#1058)

Items excluded from stock at stocking time (Return / Dispose / Quarantine)
are now tracked to resolution instead of only being mentioned in a toast.

- Donation lines gain a disposal status (Pending -> Resolved) with resolved
  date, user and notes; stocking seeds the excluded lines to Pending.
- A 'Mark Resolved' action records who/when and posts an audit note to the
  donation's chatter for accountability.
- A dedicated 'Non-Accepted Items' list + menu under Monitoring lists the
  excluded items (default Pending), filterable and groupable by action; the
  donation form's 'Items Not Accepted for Stock' table shows the disposal
  status and a resolve button.

* fix(drims): allow adding donation items with a product while announced (#1055)

* fix(drims): show non-accepted donation items only in their table + drop empty rows (#1058)

* feat(drims): launch a wizard to enter received quantities on Mark Received (#1163)

* fix(drims): stop the filler-row rule hiding "Add a line" on donations

A new donation offered no way to add items. The stylesheet that hides Odoo's
blank filler rows on the donation tables excluded
.o_field_x2many_list_row_add from the rows it hid, on the assumption that
class sits on the <tr>. In Odoo 19 it is on the <td> and the row itself is
<tr class="d-print-none">, so the exclusion never matched and the rule hid
the "Add a line" row along with the fillers.

This was worse than being unable to add items. OP#1076 also requires at
least one item before a donation can be saved, so with the add row hidden
there was no way to satisfy that rule: a donation could not be created at
all. The two rules closed the door on each other.

Match the fillers by having no class instead, which is what actually
distinguishes them - data rows carry o_data_row and the add row carries
d-print-none. If Odoo ever gives fillers a class the rule stops applying and
the blank rows come back, which is a cosmetic regression rather than a form
nobody can enter data into.

Also switches the donation line's hidden quantity field from invisible to
column_invisible. Inside a list the former blanks the cells but still renders
the column, which is why an empty "Quantity" column sat between Pledged and
Unit.

* fix(drims_sl_demo): walk donations through the reworked lifecycle

The SL demo generator could no longer produce donations. It assumed a
donation is created in "announced" and that Mark Received copies the pledged
quantity, both of which OP#1076 changed: donations now start in draft, and
the received quantity is entered by hand and required before a donation can
be marked received.

Walk from draft with an explicit announce step, and record demo donations as
arriving in full so the receive step has quantities to work with.

Caught by CI, not locally: spp_drims_sl_demo is two levels down the
dependency chain from spp_drims and a spp_drims-only run cannot see it.

* fix(spp_drims): address the donations review — versions, zero receipts, negative guard

Version bumps on spp_drims and spp_drims_sl_demo. Without one, a deployment
loads the new Python without upgrading the module, and the new draft state is a
data record: _get_default_state searches for code 'draft', finds nothing, and
every new donation is created with no state at all, so no lifecycle button
matches. The changelog entries record that, and state the donation line's
Description removal explicitly — it was replaced by the product and quantity
columns during the rework, and the database column is left in place, so
existing values are retained but no longer reachable through the ORM.

Received is no longer required by the items list. The web client reads 0.0 on a
float as "not set", so the attribute refused to save a line for an item that
was pledged and never arrived — received 0, variance the full shortfall — while
the wizard wrote exactly that and the server rule only asks for one line above
zero. The same entry was legal in one surface and impossible in the other; the
rule that matters stays in action_mark_received.

A negative received quantity is now refused. It slipped past "at least one line
above zero" whenever another line was positive, then reached the receipt
picking and failed there as an obscure stock error, a long way from the field
that caused it.

test_1108 asserted through Odoo's defaulting machinery rather than the fix: it
passed whether or not the view still carried the product context that OP#1108
added. It now reads that context off the items list — located by its Pledged
column, since the form holds other lists whose product_id carries no such
context.

* fix(spp_drims): let one rule refuse donations on a closed incident

With #416 merged, its create-time _drims_ensure_open guard and this branch's
_check_incident_not_closed constraint both applied. The create-time one fired
first, so the constraint was masked and the test asserting it failed.

The constraint is the one to keep, as flagged in both reviews: it is strictly
broader, catching an existing donation re-pointed at a closed incident, which a
create-only check cannot see. OP#1158's test now expects the ValidationError it
raises; the rule it asserts is unchanged.
gonzalesedwin1123 added a commit that referenced this pull request Aug 20, 2026
Drift back-merge per the staging plan (19.0 advanced 11 commits since the
batch-2 cut: #303, #322, #323, #390, #391, #393, #411, #412, #414, #416,
#433). Conflicts were confined to spp_farmer_registry_demo metadata:
batch 2's #336 demo fix claimed version 19.0.2.1.2, which 19.0 has since
consumed (#412=.2, #322=.3, #323=.4). Resolution re-bumps the batch-2
farmer-demo change to 19.0.2.1.5 with its HISTORY entry moved on top;
README.rst/index.html regenerated via the pinned oca-gen hook. The
change ships no migration directory, so the collision was metadata-only.
The generator code change itself auto-merged cleanly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants