From c528d581c2751847fd139420e229ca5809fd9592 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sat, 25 Apr 2026 00:05:48 -0400 Subject: [PATCH] =?UTF-8?q?feat(jobs):=20Phase=205=20=E2=80=94=20fp.job=20?= =?UTF-8?q?reports=20(sticker=20+=20traveller)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two parallel report definitions for the native job model: 1. Job Sticker (6x4 inch custom paperformat) bound to fp.job. Prints WH/JOB/... ID, customer, SO, qty, due date, recipe, step progress. QR encodes /fp/job/ for scan-to-job navigation. 2. Job Traveller bound to fp.job, A4 portrait. Job header + all fp.job.step rows in sequence order with operator sign-off column. Coexists with fusion_plating_reports' MO/WO bindings — both print menus stay live during migration. Deferred reports (use existing during migration; rebind at cutover): - BoL, Packing Slip, Invoice (read from SO, no fp.job change needed) - WO Margin (cost rollup; rebuild against fp.job.step.cost_total in phase-end polish) Adds fusion_plating_reports to fusion_plating_jobs depends. Tests deferred to post-Tailscale-restore: 3 new tests verify report actions are registered + sticker template renders without QWeb errors. Module file content verified locally as well-formed XML. Manifest 19.0.1.7.0 → 19.0.1.8.0. Part of: native job model migration (spec 2026-04-25) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../fusion_plating_jobs/__init__.py | 1 + .../fusion_plating_jobs/__manifest__.py | 5 +- .../fusion_plating_jobs/report/__init__.py | 3 + .../report/report_fp_job_sticker.xml | 117 ++++++++++++++++++ .../report/report_fp_job_traveller.xml | 71 +++++++++++ .../tests/test_fp_job_extensions.py | 29 +++++ 6 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 fusion_plating/fusion_plating_jobs/report/__init__.py create mode 100644 fusion_plating/fusion_plating_jobs/report/report_fp_job_sticker.xml create mode 100644 fusion_plating/fusion_plating_jobs/report/report_fp_job_traveller.xml diff --git a/fusion_plating/fusion_plating_jobs/__init__.py b/fusion_plating/fusion_plating_jobs/__init__.py index a0fdc10f..7db66946 100644 --- a/fusion_plating/fusion_plating_jobs/__init__.py +++ b/fusion_plating/fusion_plating_jobs/__init__.py @@ -1,2 +1,3 @@ # -*- coding: utf-8 -*- from . import models +from . import report diff --git a/fusion_plating/fusion_plating_jobs/__manifest__.py b/fusion_plating/fusion_plating_jobs/__manifest__.py index db9075d7..cc439664 100644 --- a/fusion_plating/fusion_plating_jobs/__manifest__.py +++ b/fusion_plating/fusion_plating_jobs/__manifest__.py @@ -3,7 +3,7 @@ # License OPL-1 (Odoo Proprietary License v1.0) { 'name': 'Fusion Plating — Native Jobs', - 'version': '19.0.1.7.0', + 'version': '19.0.1.8.0', 'category': 'Manufacturing/Plating', 'summary': 'Native plating job model — replaces mrp.production / mrp.workorder bridge.', 'description': """ @@ -33,10 +33,13 @@ full design rationale and §6.2 of the implementation plan for task list. 'fusion_plating_portal', # fusion.plating.portal.job 'fusion_plating_quality', # fusion.plating.customer.spec, fusion.plating.quality.hold 'fusion_plating_receiving', # fp.racking.inspection (Phase 3) + 'fusion_plating_reports', # paperformat helpers, customer_line_header (Phase 5) ], 'data': [ 'security/ir.model.access.csv', 'views/res_config_settings_views.xml', + 'report/report_fp_job_sticker.xml', + 'report/report_fp_job_traveller.xml', ], 'installable': True, 'application': False, diff --git a/fusion_plating/fusion_plating_jobs/report/__init__.py b/fusion_plating/fusion_plating_jobs/report/__init__.py new file mode 100644 index 00000000..24f7bb39 --- /dev/null +++ b/fusion_plating/fusion_plating_jobs/report/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Nexa Systems Inc. +# License OPL-1 (Odoo Proprietary License v1.0) diff --git a/fusion_plating/fusion_plating_jobs/report/report_fp_job_sticker.xml b/fusion_plating/fusion_plating_jobs/report/report_fp_job_sticker.xml new file mode 100644 index 00000000..8e4906b4 --- /dev/null +++ b/fusion_plating/fusion_plating_jobs/report/report_fp_job_sticker.xml @@ -0,0 +1,117 @@ + + + + + + FP Job Sticker (6x4") + custom + 152 + 102 + Portrait + 0 + 0 + 0 + 0 + + 0 + + 300 + + + + Job Sticker + fp.job + qweb-pdf + fusion_plating_jobs.report_fp_job_sticker_template + fusion_plating_jobs.report_fp_job_sticker_template + 'Job Sticker - %s' % (object.name or '').replace('/', '-') + + report + + + + + + diff --git a/fusion_plating/fusion_plating_jobs/report/report_fp_job_traveller.xml b/fusion_plating/fusion_plating_jobs/report/report_fp_job_traveller.xml new file mode 100644 index 00000000..d83afb28 --- /dev/null +++ b/fusion_plating/fusion_plating_jobs/report/report_fp_job_traveller.xml @@ -0,0 +1,71 @@ + + + + + + Job Traveller + fp.job + qweb-pdf + fusion_plating_jobs.report_fp_job_traveller_template + fusion_plating_jobs.report_fp_job_traveller_template + 'Traveller - %s' % (object.name or '').replace('/', '-') + + report + + + + + diff --git a/fusion_plating/fusion_plating_jobs/tests/test_fp_job_extensions.py b/fusion_plating/fusion_plating_jobs/tests/test_fp_job_extensions.py index 1324a3c9..12ac2131 100644 --- a/fusion_plating/fusion_plating_jobs/tests/test_fp_job_extensions.py +++ b/fusion_plating/fusion_plating_jobs/tests/test_fp_job_extensions.py @@ -503,3 +503,32 @@ class TestPhase4Refactors(TransactionCase): }) job.action_confirm() # Should not raise even with no templates self.assertEqual(job.state, 'confirmed') + + +class TestReports(TransactionCase): + def setUp(self): + super().setUp() + self.partner = self.env['res.partner'].create({'name': 'C'}) + self.product = self.env['product.product'].create({'name': 'P'}) + + def test_sticker_report_action_exists(self): + action = self.env.ref('fusion_plating_jobs.action_report_fp_job_sticker', raise_if_not_found=False) + self.assertTrue(action) + self.assertEqual(action.model, 'fp.job') + + def test_traveller_report_action_exists(self): + action = self.env.ref('fusion_plating_jobs.action_report_fp_job_traveller', raise_if_not_found=False) + self.assertTrue(action) + self.assertEqual(action.model, 'fp.job') + + def test_sticker_renders_for_a_job(self): + # Smoke test: the QWeb template should render without error. + job = self.env['fp.job'].create({ + 'partner_id': self.partner.id, + 'product_id': self.product.id, + 'qty': 1.0, + }) + report = self.env.ref('fusion_plating_jobs.action_report_fp_job_sticker') + # Render HTML (faster than PDF; doesn't need wkhtmltopdf) + html, _ = report._render_qweb_html(report.report_name, job.ids) + self.assertIn(job.name, html.decode() if isinstance(html, bytes) else html)