fix(spp_drims): only let a dispatch ship what its request approved - #391
Open
emjay0921 wants to merge 1 commit into
Open
fix(spp_drims): only let a dispatch ship what its request approved#391emjay0921 wants to merge 1 commit into
emjay0921 wants to merge 1 commit into
Conversation
A dispatch is generated from an approved request, but its Operations tab stayed editable in Ready state, so two things could be smuggled past the approval workflow. Both were reproduced on a dev instance: each validated successfully with no warning. "Add a Product" attached a move no request line asked for, and 50 units of a never-requested item shipped as done. Separately, using the padlock to unlock the picking made Demand editable again, so an approved line went from 100 to 150 and shipped 150 against a request for 100 with 100 allocated. The request line was left reading dispatched=150 against requested=100, and the unapproved item's unit cost was booked into the incident's drims_distributed_value as distributed relief value. Add _check_drims_dispatch_matches_request, called at the top of button_validate: every live move has to trace back to a line of this request, and nothing may ship beyond that line's allocated quantity, counting what earlier dispatches already shipped for it. Allocation is itself capped at the requested quantity by _allocate_stock_fifo, so comparing against quantity_allocated transitively enforces the approved amount rather than introducing a second notion of it. The check is keyed on drims_request_line_id, not Odoo's `additional` flag. `additional` is only set when a line is added through the form, so a move created over RPC or by an import leaves it False and would slip past a check based on it; there is a test asserting that precondition. On the form, lock the line-up through the Operations field's `options`: no "Add a Product" row and no row delete for a request dispatch. These have to be `options` entries rather than bare create/delete attributes on the field — the x2many passes its options dict through as crudOptions and evaluates each entry as a domain against the parent record, whereas bare attributes on a field tag are ignored. Quantity is deliberately left editable, since entering less than Demand is how a partial dispatch and its backorder are produced; making the list readonly wholesale would break that. OP#1057
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## 19.0 #391 +/- ##
==========================================
+ Coverage 72.27% 72.28% +0.01%
==========================================
Files 1010 1008 -2
Lines 60741 60759 +18
==========================================
+ Hits 43900 43922 +22
+ Misses 16841 16837 -4
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why is this change needed?
A DRIMS dispatch is generated from an approved request, but its Operations tab stayed editable in Ready state. Reproduced on a dev instance before changing anything — a request for 100 units of one approved item yielded two ways past the approval workflow, both validating successfully with no warning:
done,drims_request_line_idemptyThe ticket describes the first. The second has the same impact and is covered by the ticket's own Impact section ("inventory records will reflect quantities that don't match the approved request"), so it is fixed here too.
Fallout in both cases: the request line was left reading
dispatched=150againstrequested=100, and the unapproved item's unit cost was booked into the incident'sdrims_distributed_valueas distributed relief value. In a humanitarian context that is unauthorised aid movement plus a corrupted distribution figure.How was the change implemented?
_check_drims_dispatch_matches_request()(stock_picking.py), called at the top ofbutton_validate:quantity_allocated, counting what earlier dispatches already shipped for it.Two deliberate choices:
drims_request_line_id, not Odoo'sadditionalflag.additionallooks like the natural discriminator, but it is only set when a line is added through the form — a move created over RPC or by an import leaves itFalseand would slip straight past a check based on it. There is a test asserting that precondition so the reasoning is not lost later.quantity_allocatedtransitively enforces approval, because_allocate_stock_fifonever allocates beyondquantity_requested. That avoids introducing a second, parallel notion of "the approved quantity" that could drift.Form lock — the Operations field's
optionsnow carrycreateanddeletedomains ondrims_type, removing the "Add a Product" row and the row-delete for a request dispatch.Worth flagging for reviewers, since it is easy to get wrong: these must be
optionsentries, not barecreate=/delete=attributes on the<field>tag. The x2many passes itsoptionsdict through ascrudOptions(x2ManyField.extractProps) anduseActiveActionsevaluates each entry as a domain against the parent record; bare attributes on a field tag are silently ignored, and only the inner<list>honours those, as static booleans. Both rendered controls were traced to their gates —displayRowCreates→canCreate→activeActions.create, andhasX2ManyAction→activeActions.delete(list_renderer.xml:346).Quantity is left editable on purpose. Entering less than Demand is how a partial dispatch and its backorder are produced, so a blanket
readonlyon the moves list would have broken that flow. Core already makesproduct_idreadonly unlessadditionalis set, and Demand readonly until the padlock is used, so the view change stays narrow.New unit tests
spp_drims/tests/test_dispatch_line_lock.py— 9 tests:additional(the RPC/import hole).Unit tests executed by the author
Full module suite:
All 9 new tests confirmed executing by name in the log rather than inferred from the total.
./spp lint(ruff, ruff-format, prettier) passes on all four files. Ruff flagged B023 on the first draft — lambdas capturing loop variables, harmless as written sincefiltered()runs immediately, but rewritten with comprehensions rather than suppressed.Verified in the running app on a fresh database, not only in tests:
assigned, nothing ships;is_quantity_done_editable=True, and a write of 90 persists) while Demand stays read-only;request_dispatchand has not leaked onto ordinary Inventory operations or other DRIMS picking types.How to test manually
Requires a DRIMS warehouse with stock and an approved, allocated request.
demo-scripts/ds-openspp2-op1057-spp_drims-20260803-02.pybuilds the whole fixture set on a fresh DB;…-01.pyis a 4/4 regression check of the server-side guard.Related links
Notes for the reviewer
readme/HISTORY.mdand the manifest version are deliberately untouched — per our convention those land on19.0after merge, to avoid conflicts between concurrent PRs on the same module. The entry for this change would befix(spp_drims): only let a dispatch ship what its request approved.This branch is independent of #390 (OP#1087). Both edit
button_validate, but in different hunks — this one adds a call at the top, #390 adds state settling aftersuper(). They should merge cleanly in either order; whichever lands second may want a quick rebase check.Unrelated defect noticed while building fixtures, left alone:
spp_drims/models/constants.pydeclaresPRIORITY_LOW/MEDIUM/HIGH, but the shipped vocabulary data isroutine/urgent/critical, and transport modes areroad/air/sea/footrather thantruck. Those constants appear to be dead and will mislead whoever trusts them next — worth its own ticket.🤖 Generated with Claude Code