feat(reports): packing slip in Print menu of SO, Work Order, Receiving

Generalize the delivery packing slip template to be model-agnostic
(branches on doc._name to resolve the sale order + ship-to for
sale.order / fp.job / fp.receiving / fusion.plating.delivery) and add
three report actions bound to sale.order, fp.job and fp.receiving so the
packing slip appears in each one's Print menu (delivery already had it).
Uses _scheduled / _notes so it never AttributeErrors on models without
scheduled_date / notes. Declare the fusion_plating_receiving dep on
reports (already transitive via logistics) for the fp.receiving binding.

Verified on entech: real content for SO-30102, WO-30102, RCV-30103; all
four Print-menu bindings live.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-06-04 15:25:52 -04:00
parent c97a0d985c
commit 197030a188
2 changed files with 76 additions and 14 deletions

View File

@@ -3,7 +3,7 @@
# License OPL-1 (Odoo Proprietary License v1.0)
{
'name': 'Fusion Plating — Reports',
'version': '19.0.11.34.0',
'version': '19.0.11.36.0',
'category': 'Manufacturing/Plating',
'summary': 'PDF reports for Fusion Plating: quote, SO, WO, packing, BoL, CoC, invoice, receipt, quality + compliance.',
'depends': [
@@ -25,6 +25,11 @@
# creates a cycle. Our only fp.job touchpoint is wo_scan.py which
# uses runtime env.get('fp.job') — safe without the manifest dep.
'fusion_plating_logistics',
# Needed for the packing-slip Print binding on fp.receiving
# (binding_model_id ref). Already a transitive dep via logistics;
# declared explicitly so the ref is robust. No cycle — receiving
# does not depend on reports.
'fusion_plating_receiving',
],
'data': [
'security/ir.model.access.csv',

View File

@@ -549,18 +549,36 @@
<t t-call="fusion_plating_reports.fp_portrait_styles"/>
<t t-call="fusion_plating_reports.fp_packing_slip_styles"/>
<!-- Resolve SO + lines from the delivery's job_ref
(mirrors the BoL report's resolution). -->
<t t-set="_job" t-value="env['fp.job'].sudo().search([('name', '=', doc.job_ref)], limit=1) if doc.job_ref else env['fp.job']"/>
<t t-set="_so" t-value="_job.sale_order_id if _job else False"/>
<!-- Model-agnostic resolution. This ONE template backs
four report actions (sale.order, fp.job,
fp.receiving, fusion.plating.delivery) so the
packing slip prints from any of those screens.
Resolve the sale order + ship-to per doc type, then
render a common layout from the SO lines. (Template
id kept as "...delivery..." for back-compat with the
action + Python that reference it.) -->
<t t-set="m" t-value="doc._name"/>
<t t-set="_so" t-value="False"/>
<t t-if="m == 'sale.order'">
<t t-set="_so" t-value="doc"/>
</t>
<t t-elif="m == 'fp.job' or m == 'fp.receiving'">
<t t-set="_so" t-value="doc.sale_order_id"/>
</t>
<t t-elif="m == 'fusion.plating.delivery'">
<t t-set="_dlv_job" t-value="env['fp.job'].sudo().search([('name', '=', doc.job_ref)], limit=1) if doc.job_ref else env['fp.job']"/>
<t t-set="_so" t-value="_dlv_job.sale_order_id if _dlv_job else False"/>
</t>
<t t-set="_lines" t-value="_so.order_line.filtered(lambda l: l.product_id and not l.display_type and l.product_uom_qty &gt; 0) if _so else False"/>
<t t-set="bill_partner" t-value="(_so.partner_invoice_id if _so and _so.partner_invoice_id else (doc.partner_id.commercial_partner_id or doc.partner_id))"/>
<t t-set="ship_partner" t-value="doc.delivery_address_id or doc.partner_id"/>
<t t-set="has_carrier" t-value="'x_fc_carrier_id' in doc._fields and doc.x_fc_carrier_id"/>
<t t-set="ship_partner" t-value="(doc.delivery_address_id or doc.partner_id) if m == 'fusion.plating.delivery' else ((_so.partner_shipping_id or _so.partner_id) if _so else doc.partner_id)"/>
<t t-set="bill_partner" t-value="(_so.partner_invoice_id if _so and _so.partner_invoice_id else (ship_partner.commercial_partner_id or ship_partner))"/>
<t t-set="has_carrier" t-value="m == 'fusion.plating.delivery' and 'x_fc_carrier_id' in doc._fields and doc.x_fc_carrier_id"/>
<t t-set="ship_via" t-value="(doc.x_fc_carrier_id.name if has_carrier else (_so.x_fc_ship_via if _so and 'x_fc_ship_via' in _so._fields and _so.x_fc_ship_via else 'CUSTOMER PICKUP'))"/>
<t t-set="tracking_text" t-value="'Ready for pick up' if not has_carrier else '—'"/>
<t t-set="po_number" t-value="(_so.client_order_ref if _so and _so.client_order_ref else '')"/>
<t t-set="_scheduled" t-value="doc.scheduled_date if m == 'fusion.plating.delivery' else (doc.commitment_date if m == 'sale.order' else (_so.commitment_date if _so else False))"/>
<t t-set="_notes" t-value="doc.notes if m == 'fusion.plating.delivery' else (doc.note if m == 'sale.order' else False)"/>
<t t-set="so_name_raw" t-value="_so.name if _so else (doc.name or '')"/>
<t t-set="ps_number" t-value="so_name_raw.rsplit('-', 1)[-1] if '-' in so_name_raw else so_name_raw"/>
@@ -622,8 +640,8 @@
<tr>
<td><span t-esc="ship_via"/></td>
<td>
<t t-if="doc.scheduled_date">
<span t-field="doc.scheduled_date" t-options="{'widget': 'date'}"/>
<t t-if="_scheduled">
<span t-out="_scheduled" t-options="{'widget': 'date'}"/>
</t>
<t t-else=""></t>
</td>
@@ -639,15 +657,15 @@
</t>
<t t-else="">
<p style="margin-top: 10px; color: #555;">
No order lines are linked to this delivery
(job <span t-esc="doc.job_ref or doc.name"/>).
No order lines found for this document
(<span t-esc="doc.name or ''"/>).
</p>
</t>
<t t-if="doc.notes">
<t t-if="_notes">
<div style="margin-top: 10px;">
<strong>Notes:</strong>
<div t-field="doc.notes"/>
<div t-out="_notes"/>
</div>
</t>
@@ -671,4 +689,43 @@
<field name="paperformat_id" ref="paperformat_fp_a4_portrait"/>
</record>
<!-- Same packing slip, exposed in the Print menu of the Sale Order,
Work Order (fp.job) and Receiving/Shipping (fp.receiving) screens.
All reuse the model-agnostic template above. -->
<record id="action_report_fp_packing_slip_so_portrait" model="ir.actions.report">
<field name="name">Packing Slip</field>
<field name="model">sale.order</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">fusion_plating_reports.report_fp_packing_slip_delivery_portrait</field>
<field name="report_file">fusion_plating_reports.report_fp_packing_slip_delivery_portrait</field>
<field name="print_report_name">'Packing Slip - %s' % (object.name or '')</field>
<field name="binding_model_id" ref="sale.model_sale_order"/>
<field name="binding_type">report</field>
<field name="paperformat_id" ref="paperformat_fp_a4_portrait"/>
</record>
<record id="action_report_fp_packing_slip_job_portrait" model="ir.actions.report">
<field name="name">Packing Slip</field>
<field name="model">fp.job</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">fusion_plating_reports.report_fp_packing_slip_delivery_portrait</field>
<field name="report_file">fusion_plating_reports.report_fp_packing_slip_delivery_portrait</field>
<field name="print_report_name">'Packing Slip - %s' % (object.name or '')</field>
<field name="binding_model_id" ref="fusion_plating.model_fp_job"/>
<field name="binding_type">report</field>
<field name="paperformat_id" ref="paperformat_fp_a4_portrait"/>
</record>
<record id="action_report_fp_packing_slip_receiving_portrait" model="ir.actions.report">
<field name="name">Packing Slip</field>
<field name="model">fp.receiving</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">fusion_plating_reports.report_fp_packing_slip_delivery_portrait</field>
<field name="report_file">fusion_plating_reports.report_fp_packing_slip_delivery_portrait</field>
<field name="print_report_name">'Packing Slip - %s' % (object.name or '')</field>
<field name="binding_model_id" ref="fusion_plating_receiving.model_fp_receiving"/>
<field name="binding_type">report</field>
<field name="paperformat_id" ref="paperformat_fp_a4_portrait"/>
</record>
</odoo>