feat(fusion_plating): internal sticker = external layout w/ internal notes + Receiving print buttons
- Internal Job Sticker is no longer a separate Layout A: it's now a COPY of the External sticker (Layout B, one per box, logo + WO + BOX + QR + rail fields + prominent PLATING THICKNESS banner) but feeds the INTERNAL description and labels its notes "INTERNAL NOTES" so the shop copy can't be confused with the customer copy. The old Layout-A body template is deleted. - Removed the Internal sticker from the fp.job Print menu (binding_model_id -> False); it now prints from the Receiving screen instead. - Added "External Sticker" + "Internal Sticker" print buttons to the fp.receiving form header (shown once a WO exists). Each renders one label per tracked box for the receiving's work order (passes a single WO so the SO-scoped box loop doesn't reprint each label per job). Verified on entech (WO-30094 / RCV-30096): internal renders Layout B with the internal description + INTERNAL NOTES; external unchanged; both receiving buttons return the right report actions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
# reaches state='done'.
|
||||
|
||||
from odoo import _, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class FpReceiving(models.Model):
|
||||
@@ -65,3 +66,34 @@ class FpReceiving(models.Model):
|
||||
if len(jobs) == 1:
|
||||
action.update({'view_mode': 'form', 'res_id': jobs.id})
|
||||
return action
|
||||
|
||||
# ---- Sticker printing from the Receiving screen (2026-06-04) ----------
|
||||
# Both stickers loop the SO's boxes (one label per box). Pass a SINGLE
|
||||
# work order: the box loop is sale-order-scoped, so feeding every job
|
||||
# would reprint each box label once per job. One job → exactly one label
|
||||
# per box. Falls back to a single 1/1 label when no boxes exist yet.
|
||||
def _fp_sticker_jobs(self):
|
||||
self.ensure_one()
|
||||
if not self.sale_order_id:
|
||||
return self.env['fp.job']
|
||||
return self.env['fp.job'].sudo().search(
|
||||
[('sale_order_id', '=', self.sale_order_id.id)], order='id', limit=1)
|
||||
|
||||
def _fp_print_sticker(self, xmlid):
|
||||
self.ensure_one()
|
||||
jobs = self._fp_sticker_jobs()
|
||||
if not jobs:
|
||||
raise UserError(_(
|
||||
'No work order exists for this receiving yet — create the '
|
||||
'Work Order before printing stickers.'))
|
||||
return self.env.ref(xmlid).report_action(jobs)
|
||||
|
||||
def action_print_external_sticker(self):
|
||||
"""Customer (external) box sticker(s) for this receiving's WO."""
|
||||
return self._fp_print_sticker(
|
||||
'fusion_plating_jobs.action_report_fp_job_sticker')
|
||||
|
||||
def action_print_internal_sticker(self):
|
||||
"""Shop (internal) box sticker(s) — same layout, internal notes."""
|
||||
return self._fp_print_sticker(
|
||||
'fusion_plating_jobs.action_report_fp_job_sticker_internal')
|
||||
|
||||
@@ -57,8 +57,11 @@
|
||||
<field name="report_name">fusion_plating_jobs.report_fp_job_sticker_internal_template</field>
|
||||
<field name="report_file">fusion_plating_jobs.report_fp_job_sticker_internal_template</field>
|
||||
<field name="print_report_name">'Internal Job Sticker - %s' % (object.name or '').replace('/', '-')</field>
|
||||
<field name="binding_model_id" ref="fusion_plating.model_fp_job"/>
|
||||
<field name="binding_type">report</field>
|
||||
<!-- NOT bound to the fp.job Print menu (removed from the list per
|
||||
request). Printed via the Receiving screen button
|
||||
(fp.receiving.action_print_internal_sticker). eval=False clears
|
||||
any binding a prior install left in the DB. -->
|
||||
<field name="binding_model_id" eval="False"/>
|
||||
<field name="paperformat_id" ref="paperformat_fp_job_sticker"/>
|
||||
</record>
|
||||
|
||||
@@ -121,74 +124,6 @@
|
||||
</style>
|
||||
</template>
|
||||
|
||||
<!-- ===================== Internal body — Layout A ===================== -->
|
||||
<template id="fp_job_internal_body">
|
||||
<div class="label-page"><div class="label">
|
||||
<table class="fpt">
|
||||
<tr style="height:20mm" class="rule">
|
||||
<td class="band pad">
|
||||
<span style="float:right;text-align:right">
|
||||
<t t-if="_logo"><span style="display:inline-block;background:#fff;padding:0.8mm 1.6mm;border-radius:1mm"><img t-att-src="image_data_uri(_logo)" style="max-height:8mm;max-width:38mm;vertical-align:middle"/></span></t>
|
||||
<div style="margin-top:1.6mm"><span class="tag">INTERNAL</span></div>
|
||||
</span>
|
||||
<span class="lbl" style="color:#fff">Work Order</span>
|
||||
<div style="font-size:30pt;font-weight:900;line-height:0.95"><t t-esc="d['wo']"/></div>
|
||||
</td>
|
||||
<td style="width:31mm;border-left:0.9mm solid #000;text-align:center;vertical-align:middle;padding:1mm">
|
||||
<span class="qfwrap-int"><img t-att-src="_qr"/></span>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Part# (dominant) + Customer moved up here. Customer is short +
|
||||
consistent (name stripped for privacy) so it needs little room;
|
||||
Part# gets the bulk. MASK/BAKE flags float at the Part# edge. -->
|
||||
<tr style="height:13mm" class="rule">
|
||||
<td style="padding:0" colspan="2"><table class="fpt"><tr>
|
||||
<td class="pad vrule" style="width:72%">
|
||||
<span style="float:right">
|
||||
<t t-if="d['mask']"><span class="badge">MASK</span></t>
|
||||
<t t-if="d['bake']"><span class="badge">BAKE</span></t>
|
||||
</span>
|
||||
<span class="lbl">Part#</span>
|
||||
<span style="font-size:18pt;font-weight:900"> <t t-esc="d['part'] or '-'"/></span>
|
||||
<t t-if="d['rev']"><span style="font-size:11pt;font-weight:bold"> Rev <t t-esc="d['rev']"/></span></t>
|
||||
</td>
|
||||
<td class="pad" style="width:28%">
|
||||
<span class="lbl">Customer</span>
|
||||
<span style="font-size:13pt;font-weight:900;display:block;line-height:1.05"><t t-esc="d['customer']"/></span>
|
||||
</td>
|
||||
</tr></table></td>
|
||||
</tr>
|
||||
<!-- PO# / Qty / Due / Thk. Label stacks TIGHT on the value: no <br/>
|
||||
(.lbl is display:block, so the value sits directly beneath it —
|
||||
the old <br/> added a blank line = the label/data gap). Thk gets
|
||||
the widest column + nowrap so a thickness RANGE stays on one line;
|
||||
PO#/Qty/Due trimmed to feed it. -->
|
||||
<tr style="height:12mm" class="rule">
|
||||
<td style="padding:0" colspan="2"><table class="fpt"><tr>
|
||||
<td class="pad vrule" style="width:25%"><span class="lbl">PO#</span><span style="font-size:13pt;font-weight:900;display:block"><t t-esc="d['po'] or '-'"/></span></td>
|
||||
<td class="pad vrule" style="width:13%"><span class="lbl">Qty</span><span style="font-size:14pt;font-weight:900;display:block"><t t-esc="d['qty']"/></span></td>
|
||||
<td class="pad vrule" style="width:26%"><span class="lbl">Due</span><span style="font-size:12pt;font-weight:bold;display:block"><t t-esc="d['due'] or '-'"/></span></td>
|
||||
<td class="pad" style="width:36%"><span class="lbl">Thk</span><span style="font-size:11pt;font-weight:bold;display:block;white-space:nowrap"><t t-esc="d['thk'] or '-'"/></span></td>
|
||||
</tr></table></td>
|
||||
</tr>
|
||||
<t t-if="d['bake']">
|
||||
<tr style="height:13mm" class="rule">
|
||||
<td class="pad" colspan="2" style="vertical-align:top;padding-top:1.5mm">
|
||||
<span class="inshead">BAKE</span>
|
||||
<span class="instext" style="font-size:10pt;line-height:1.18"> <t t-esc="d['bake']"/></span>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
<tr>
|
||||
<td class="pad" colspan="2" style="vertical-align:top;padding:1.5mm 2.5mm 3.5mm 2.5mm;overflow:hidden">
|
||||
<span class="inshead">NOTES</span>
|
||||
<div class="instext" t-att-style="'font-size:%spt;line-height:1.18;margin-top:1.5mm' % _note_pt"><t t-esc="_note or '-'"/></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div></div>
|
||||
</template>
|
||||
|
||||
<!-- ===================== External body — Layout B ===================== -->
|
||||
<template id="fp_job_external_body">
|
||||
<div class="label-page"><div class="label">
|
||||
@@ -251,7 +186,7 @@
|
||||
<div class="instext" style="font-size:10pt;line-height:1.22;margin-top:1mm"><t t-esc="d['bake']"/></div>
|
||||
</div>
|
||||
</t>
|
||||
<div class="m-notes"><span class="inshead">NOTES</span>
|
||||
<div class="m-notes"><span class="inshead"><t t-esc="_notes_label or 'NOTES'"/></span>
|
||||
<div class="instext" t-att-style="'font-size:%spt;line-height:1.25;margin-top:1.3mm' % _note_pt"><t t-esc="_note or '-'"/></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -259,17 +194,35 @@
|
||||
</template>
|
||||
|
||||
<!-- ===================== Internal outer (per job) ===================== -->
|
||||
<!-- Internal sticker = a COPY of the External (Layout B, one per box) but
|
||||
showing the INTERNAL description instead of the customer-facing notes,
|
||||
and a "INTERNAL NOTES" header so the shop copy can't be confused with
|
||||
the customer copy. Identical rail/QR/logo/box otherwise. -->
|
||||
<template id="report_fp_job_sticker_internal_template">
|
||||
<t t-call="web.html_container">
|
||||
<t t-call="fusion_plating_jobs.fp_job_sticker_styles"/>
|
||||
<t t-foreach="docs" t-as="job">
|
||||
<t t-set="d" t-value="job._fp_sticker_data()"/>
|
||||
<t t-set="_note" t-value="d['internal_notes']"/>
|
||||
<t t-set="_notes_label" t-value="'INTERNAL NOTES'"/>
|
||||
<t t-set="_note_pt" t-value="job._fp_note_pt(_note)"/>
|
||||
<t t-set="_base" t-value="job.env['ir.config_parameter'].sudo().get_param('web.base.url', '')"/>
|
||||
<t t-set="_qr" t-value="job.env['ir.actions.report'].sudo().barcode_data_uri('QR', _base + '/fp/job/' + str(job.id), width=1000, height=1000)"/>
|
||||
<t t-set="_logo" t-value="job.env.company.logo or job.env.company.logo_web or job.env.company.partner_id.image_1920 or False"/>
|
||||
<t t-call="fusion_plating_jobs.fp_job_internal_body"/>
|
||||
<t t-set="_base" t-value="job.env['ir.config_parameter'].sudo().get_param('web.base.url', '')"/>
|
||||
<t t-set="boxes" t-value="job._fp_sticker_boxes()"/>
|
||||
<t t-if="boxes">
|
||||
<t t-foreach="boxes" t-as="box">
|
||||
<t t-set="_box_num" t-value="box.box_number"/>
|
||||
<t t-set="_box_cnt" t-value="box.box_count or len(boxes)"/>
|
||||
<t t-set="_qr" t-value="job.env['ir.actions.report'].sudo().barcode_data_uri('QR', _base + '/fp/box/' + str(box.id), width=1000, height=1000)"/>
|
||||
<t t-call="fusion_plating_jobs.fp_job_external_body"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-set="_box_num" t-value="1"/>
|
||||
<t t-set="_box_cnt" t-value="1"/>
|
||||
<t t-set="_qr" t-value="job.env['ir.actions.report'].sudo().barcode_data_uri('QR', _base + '/fp/job/' + str(job.id), width=1000, height=1000)"/>
|
||||
<t t-call="fusion_plating_jobs.fp_job_external_body"/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
@@ -281,6 +234,7 @@
|
||||
<t t-foreach="docs" t-as="job">
|
||||
<t t-set="d" t-value="job._fp_sticker_data()"/>
|
||||
<t t-set="_note" t-value="d['customer_notes']"/>
|
||||
<t t-set="_notes_label" t-value="'NOTES'"/>
|
||||
<t t-set="_note_pt" t-value="job._fp_note_pt(_note)"/>
|
||||
<t t-set="_logo" t-value="job.env.company.logo or job.env.company.logo_web or job.env.company.partner_id.image_1920 or False"/>
|
||||
<t t-set="_base" t-value="job.env['ir.config_parameter'].sudo().get_param('web.base.url', '')"/>
|
||||
|
||||
@@ -25,6 +25,17 @@
|
||||
class="btn-primary" icon="fa-cogs"
|
||||
invisible="not x_fc_show_work_order_btn"
|
||||
help="Open the Work Order(s) for this receiving. Hidden automatically once every linked WO is marked Done."/>
|
||||
<!-- Print box stickers for this receiving's work order — one
|
||||
label per tracked box (external = customer copy, internal
|
||||
= shop copy with internal notes). Shown once a WO exists. -->
|
||||
<button name="action_print_external_sticker"
|
||||
string="External Sticker" type="object" icon="fa-print"
|
||||
invisible="x_fc_fp_job_count == 0"
|
||||
help="Print the customer (external) box sticker(s) — one per box."/>
|
||||
<button name="action_print_internal_sticker"
|
||||
string="Internal Sticker" type="object" icon="fa-print"
|
||||
invisible="x_fc_fp_job_count == 0"
|
||||
help="Print the shop (internal) box sticker(s) — same layout, internal notes."/>
|
||||
</xpath>
|
||||
|
||||
<!-- Work Order smart button on the button_box (mirrors the
|
||||
|
||||
Reference in New Issue
Block a user