Skip to content

feat(spp_drims): Dispatch page and waybill review — locked lines, one-page waybill - #433

Open
emjay0921 wants to merge 9 commits into
19.0from
feat/1151-1150-drims-waybill-and-dispatch-page
Open

feat(spp_drims): Dispatch page and waybill review — locked lines, one-page waybill#433
emjay0921 wants to merge 9 commits into
19.0from
feat/1151-1150-drims-waybill-and-dispatch-page

Conversation

@emjay0921

Copy link
Copy Markdown
Contributor

Why is this change needed?

Two children of the Stock Allocation & Dispatch review (OP#1077), both returned from QA as passing:

  • OP#1150 — Improvements to Dispatch page. The DRIMS tab sat last on the dispatch form, behind Additional Info and Note, although it is where a dispatch is actually worked. The form also showed a Delivery Address that is meaningless for relief goods and no destination at all, and it left every field the request had already decided — operation type, source location, source document, products — editable for the dispatch's whole working life.
  • OP#1151 — Corrections to Waybill PDF. The waybill printed over two pages with columns stacked and the signature row pushed onto the second, the TO box printed blank, and any report containing a barcode or QR code answered HTTP 500 in this image.

How was the change implemented?

  • Waybill rendered, not read. The template was fixed against the rendered output rather than the source, so it fits one page with the signature row intact and states what is on the vehicle. rlPyCairo was added to docker/requirements.txt: every barcode and QR in every Odoo report goes through renderPM, reportlab 4.1 defaults that backend to rlPyCairo, and without it /report/barcode/ returns 500. Odoo's own requirements only pin a backend for win32, assuming Debian's python3-renderpm — this image does not install it.
  • Dispatch & Delivery leads the form. The tab moved to second position, right after Operations, and is labelled for what it does instead of "DRIMS". Delivery Address is replaced on a dispatch by the destination location, which is what spp.drims.request.action_create_dispatch actually populates; partner_id stays blank, and that emptiness is why the waybill's TO box printed blank.
  • A dispatch is locked to what its request approved. The header fields and everything under DRIMS Information are read-only for request dispatches only — ordinary transfers, donation receipts and returns keep core's behaviour. Quantity stays editable on purpose: entering less than Demand is how a partial dispatch and its backorder are produced (OP#1087).
  • Source Location is inert, not merely read-only. Making a many2one read-only turns it into a link to the record, which QA spotted. no_open cannot be conditional, so core's copy is hidden on a dispatch and an inert copy shown in its place — the same shape as OP#1158's hazard category.
  • Both header xpaths are qualified: core declares picking_type_id twice and location_id three times, and position="attributes" takes the first match — which for location_id is the copy nobody can see, so an unqualified xpath would have looked like nothing happened.

New unit tests

  • spp_drims/tests/test_waybill_report.py — 13 tests, asserting against the rendered PDF/HTML rather than the template.
  • spp_drims/tests/test_dispatch_page.py — 11 tests, evaluating the view's own invisible expressions per kind of picking so a change scoped to dispatches is proven not to touch transfers, receipts or returns.

Unit tests executed by the author

Full spp_drims suite on this branch, against a fresh database: 287 tests, 0 failed, 0 errors.

Worth knowing for anyone testing locally: two of the barcode tests need the rlPyCairo this PR adds, so a container image built before this branch fails them with ModuleNotFoundError: No module named 'rlPyCairo', and pip install rlPyCairo inside the runtime image cannot fix it — pycairo needs the dev headers that only the builder stage has. Rebuild the image (docker compose build openspp-dev) and they pass. CI builds the test image from docker/Dockerfile at this checkout, so it picks the dependency up on its own.

How to test manually

  1. ./spp start, then open a DRIMS request and allocate it.
  2. Create a dispatch from the request. On the dispatch form:
    • Dispatch & Delivery is the second tab, right after Operations.
    • Operation type, source document, Source Location and the DRIMS Information fields are read-only; Quantity is still editable.
    • Source Location is plain text — clicking it does not open the location record.
    • There is a destination location, and no Delivery Address field.
  3. Print the waybill: one page, signature row intact, TO box filled in, and the barcode renders instead of a 500.
  4. Repeat step 2 on a plain internal transfer, a donation receipt and a return — all keep core's editable behaviour.

Related links

Rendered the waybill and read the result rather than the template. It came
out two pages, with every column stacked and the signature row pushed onto
page 2; the TO box empty; "Total Items" reading 1 for a 200-unit
consignment; the picking's internal note printed; the quantity column
showing demand rather than what was picked; and no barcode.

Lay it out with tables instead of Bootstrap's grid. Reports are rendered by
wkhtmltopdf 0.12.6, a WebKit build with no flexbox, so row/col-* and card
collapse and each column becomes a row. A test fails if any of those
classes reappear, because the damage is invisible in the browser preview
and only shows in the PDF.

Fill the TO box from location_dest_id. partner_id is blank on a dispatch —
relief goods go to a location, not a customer — which is why the box was
empty. Print an address only when a street is recorded, since the contact
widget on a bare partner renders its name wrapped in dashes.

Show Shipped alongside Demand. A waybill accompanies the goods, so it has
to state what is actually on the vehicle; demand stays for reconciliation
at the receiving end. Drop the "Total Items" footer, which counted move
lines, and the note, which is internal. Always print the Vehicle and
Driver rows so there is somewhere to write, and print whichever of
Request or Donation matches the transaction type. Add the transaction
type, distribution area, estimated beneficiaries and distribution type.

The barcode needed two fixes and neither was in the template. reportlab
4.1 defaults renderPMBackend to rlPyCairo, which the image does not
install, so /report/barcode/ answered HTTP 500 for every URL form — every
barcode and QR code in every Odoo report, not just this one. Odoo's own
requirements only pin a backend for win32, assuming the Debian package
covers Linux. Adding rlPyCairo fixes the route. The image still did not
appear, because an <img> with a relative URL needs wkhtmltopdf to fetch it
from inside the rendering process, where web.base.url points at an
external host and port that cannot be reached; embedding it as a data URI
removes that dependency, verified by rendering with web.base.url
deliberately pointed at a dead port. A failure to build the barcode is
logged and skipped rather than raised, so it cannot stop a waybill
printing.

Also expose stock.move's drims_request_line_id and drims_donation_line_id,
which appeared in no view, so it is possible to see which request or
donation line a move fulfils. Read-only, since reassigning one by hand
would misattribute dispatched and delivered quantities. Kept in its own
file: the picking form's Operations list is defined inline in
stock.view_picking_form and cannot be extended from a separate view.

Note the ticket lists a third linkage field, drims_allocation_id, which
does not exist on the model.

OP#1151
The DRIMS tab sat last, behind Additional Info and Note, although it is
where a dispatch is actually worked. It now sits second, right after
Operations, and is labelled "Dispatch & Delivery" rather than "DRIMS".

Replace "Delivery Address" with the destination location on a dispatch.
partner_id is left blank because relief goods go to a location rather than
a customer, so the field only invited someone to fill in something
meaningless — and its emptiness is why the waybill's TO box printed blank
(OP#1151). Core hides location_dest_id for outgoing pickings and keeps an
invisible="1" copy behind stock.group_stock_multi_locations, which is why a
dispatch showed no destination at all; rather than un-hide core's copy,
which would stay invisible wherever that group is off, add a read-only one
scoped to dispatches. It is populated by
spp.drims.request.action_create_dispatch from the request's destination
warehouse.

Both changes are scoped to request dispatches, so donation receipts,
transfers, returns and plain stock pickings are untouched — asserted per
picking kind by evaluating the view's own invisible expressions.

The ticket's fourth item, removing the ability to delete a line, is already
implemented for request dispatches in OP#1057; repeating it here would mean
two xpaths setting the same attribute on the same field.

OP#1150
…waybill-and-dispatch-page

# Conflicts:
#	spp_drims/views/stock_picking_views.xml
A request dispatch is generated by action_create_dispatch, which sets the
operation type, the source location, the source document, the products and
every field under DRIMS Information. None of them is the dispatcher's to
change, but core only locks the header fields once the picking is done or
cancelled, so they stayed editable for a dispatch's whole working life.

Lock them for request dispatches only. Ordinary transfers, donation
receipts and returns keep core's behaviour. Quantity is deliberately left
editable: entering less than Demand is how a partial dispatch and its
backorder are produced (OP#1087).

Both header xpaths need qualifying. Core declares picking_type_id twice —
once in the header and once column_invisible in the moves list — and
location_id three times: a copy hidden behind
!stock.group_stock_multi_locations, the visible Source Location behind that
same group, and one in the list. position="attributes" takes the first
match, which for location_id is the invisible copy, so an unqualified xpath
would have locked a field nobody can see and looked like nothing happened.
A test asserts the lock lands on the visible field for that reason.

Also converts a round-1 xpath from @Class to hasclass(), which the UI hook
requires, and moves the test fixtures onto OP#1079's per-warehouse
allocation model.
…ad-only

QA asked whether Source Location had become clickable. It had, and round 2
is what did it: making a many2one read-only turns it into an internal link
to the record. Core sets no_open on picking_type_id already, which is why
Operation Type is inert - it does not on location_id.

no_open cannot be made conditional, because options is parsed as a static
dict and cannot reference drims_type. So core's copy is hidden on a dispatch
and an inert one shown in its place, the same shape as OP#1158's hazard
category. The replacement repeats the multi-locations group, or a
single-location installation would start seeing a Source Location field it
is not meant to have.

The view tests now ask which copy a given kind of picking actually renders,
rather than assuming one node per field.
view_stock_move_operations_drims fails the XML ID hook - "operations" is not
a view type - so it would block this branch's PR. Renamed to
view_move_form_operations_drims, matching its siblings in the same file
(view_move_form_drims, view_move_tree_drims).

Introduced by the waybill commit on this branch; never on 19.0, and
referenced nowhere else, so the rename is contained.
@emjay0921
emjay0921 marked this pull request as ready for review August 18, 2026 06:15
@codecov

codecov Bot commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 72.25%. Comparing base (0820667) to head (5be1c20).

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             19.0     #433   +/-   ##
=======================================
  Coverage   72.24%   72.25%           
=======================================
  Files         419      419           
  Lines       29813    29829   +16     
=======================================
+ Hits        21539    21553   +14     
- Misses       8274     8276    +2     
Flag Coverage Δ
spp_base_common 91.07% <ø> (ø)
spp_drims 82.41% <100.00%> (+0.16%) ⬆️
spp_drims_sl_demo 68.43% <ø> (-0.58%) ⬇️
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/stock_picking.py 78.86% <100.00%> (+2.07%) ⬆️

... and 3 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.

@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.

Strongest view-layer work of the batch — every risky claim in the PR body verified out against the Odoo 19 core source:

  • Every position="attributes" restatement is exact before the dispatch clause is added: picking_type_id (state in ('done', 'cancel')), origin (state in ['cancel', 'done']), product_id ((state != 'draft' and not additional) or move_lines_count > 0), and location_id's invisible (picking_type_code == 'incoming'). Since attribute replacement is wholesale, a misquote would have silently loosened core behaviour — none did.
  • The xpath first-match traps are real: core's first location_id is indeed the invisible groups="!stock.group_stock_multi_locations" copy, so the qualified selectors are load-bearing, not pedantry. The inert replacement copy correctly repeats the multi-locations group.
  • The barcode fix is the right architecture — a data URI removes wkhtmltopdf's dependency on fetching web.base.url from inside the renderer, which genuinely cannot work in a containerised deployment. The broad-except-warn-return-False in _get_waybill_barcode_data_uri is justified (a barcode must never stop a waybill) and both the helper and the degraded path are tested.
  • The rlPyCairo claim is CI-proven: the test image builds from this checkout's Dockerfile, and the two barcode tests require the dependency and pass.
  • The 24 tests asserting rendered output (waybill) and per-picking-kind view expressions (dispatch page) prove transfers, receipts and returns keep core behaviour.

Also a pleasing composition: this PR's UI product-lock + #391's create/delete options + #391's server-side validation guard form one coherent layered lock on the dispatch line-up, with Quantity editable throughout for the backorder flow.

1. Blocker — version bump

Per the convention Edwin ruled on (see #391): bump + HISTORY in the PR, number assigned at merge-time rebase. Here it's functionally meaningful, not just signalling: the new stock_move_views.xml and the rewritten waybill template are data, so a deploy without -u spp_drims keeps the old form and the old waybill entirely. Please also have the HISTORY entry mention the required image rebuild for rlPyCairo — that's a deploy-runbook fact, not just a local-testing one.

2. Merge-order note

Your land-this-first proposal makes sense and has been passed to Edwin with one caveat: #393 edits inside the same DRIMS-tab block this PR rewrites and renames, so whichever of #433/#393 lands second has a real rebase rather than a mechanical one. #390/#391 are compatible-but-adjacent as you say.

With the bump added, this is an approve.

Version bump with its changelog entry. Functionally required here rather than
signalling: the new stock_move_views.xml and the rewritten waybill template are
data, so a deployment that skips the upgrade keeps the old dispatch form and the
old waybill in full.

The entry also records the deployment fact that the barcode needs rlPyCairo,
added to docker/requirements.txt in this branch: upgrading the module is not
enough, the image has to be rebuilt, or any report carrying a barcode answers
HTTP 500. The waybill still prints without it, minus the barcode.
@emjay0921

Copy link
Copy Markdown
Contributor Author

Thanks — bump added, and the deploy note with it. Pushed as 5be1c20a.

1. Version bump — spp_drims 19.0.3.0.019.0.3.0.4, taking the next free patch alongside #390 (.1), #391 (.2) and #393 (.3).

You are right that this one is functional rather than signalling: stock_move_views.xml and the rewritten waybill template are data, so a deployment that skips the upgrade keeps the old form and the old waybill in full — the visible half of the ticket simply would not arrive.

The rlPyCairo rebuild is now in the changelog as a deployment note, not just a local-testing aside — thank you for the framing, it is exactly the fact a runbook needs and the wrong place for it was my PR body:

Deployment note: the waybill's barcode needs the rlPyCairo renderer, added to docker/requirements.txt in this change. Upgrading the module is not enough — the container image has to be rebuilt, or every report containing a barcode or QR code answers HTTP 500. The waybill itself still prints without it, minus the barcode.

2. Merge order — noted, and agreed on the #393 caveat: it edits inside the same DRIMS-tab block this branch rewrites and renames, so second-in is a real rebase there. Both are mine, so whichever order you set, I will take the rebase rather than leaving it to the merger. Worth adding that #393 has since had its own review addressed (f3fede6a), so the two are moving together.

spp_drims suite: 287 tests, 0 failed. pre-commit clean, README regenerated.

@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 — the one blocking item is done, and done well.

Verified on 5be1c20a: bump to 19.0.3.0.4 with the HISTORY entry, README/HTML regenerated, and the deployment note is exactly what was asked for — it says plainly that upgrading the module is not enough, the image has to be rebuilt for rlPyCairo, and it correctly records that the waybill still prints without the barcode rather than failing. That's the runbook fact captured where upgraders will see it. Full CI re-ran green after the push.

One mechanical note for merge time, not a change request: #416 (same module) is now approved carrying 19.0.3.1.0 with a migration directory named for it. If #416 lands first, 19.0.3.0.4 becomes a version decrease and won't trigger an upgrade — so per the merge-time-assignment rule, whichever of the two lands second renumbers above the other (and #416's migration folder would move with its number if that's the one renumbered). Your land-#433-first proposal avoids touching #416's migration path, which is another point in its favour; Edwin has both inputs for the queue decision.

Everything else stands from the first review: the core-rule restatements are exact, the xpath disambiguation is load-bearing and correct, the barcode architecture is right, and the 24 rendered-output/view-expression tests hold the scope to dispatches. Merge stays with Edwin per the usual flow.

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