From 6e57b3576c7ddf8a5884fc8bf6d62d6bd7d48b4f Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Fri, 24 Apr 2026 23:01:23 -0400 Subject: [PATCH] feat(jobs): add cross-module fields to fp.job via _inherit (Task 2.2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 5 of the 6 deferred fields from Phase 1 Task 1.4 land here in fusion_plating_jobs: - part_catalog_id (fp.part.catalog from configurator) - coating_config_id (fp.coating.config from configurator) - customer_spec_id (fusion.plating.customer.spec from quality) - portal_job_id (fusion.plating.portal.job from portal) - delivery_id (fusion.plating.delivery from logistics) qc_check_id deferred to Task 2.7 — its target model (fusion.plating.quality.check) still lives in fusion_plating_bridge_mrp and we don't depend on bridge_mrp from this module. Task 2.7 will address QC sourcing. 6 unit tests (5 field-presence + 1 integration creating linked records). Manifest 19.0.1.0.0 → 19.0.1.1.0. Part of: native job model migration (spec 2026-04-25) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../fusion_plating_jobs/__manifest__.py | 2 +- .../fusion_plating_jobs/models/__init__.py | 4 +- .../fusion_plating_jobs/models/fp_job.py | 39 +++++++ .../fusion_plating_jobs/tests/__init__.py | 2 + .../tests/test_fp_job_extensions.py | 100 ++++++++++++++++++ 5 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 fusion_plating/fusion_plating_jobs/models/fp_job.py create mode 100644 fusion_plating/fusion_plating_jobs/tests/__init__.py create mode 100644 fusion_plating/fusion_plating_jobs/tests/test_fp_job_extensions.py diff --git a/fusion_plating/fusion_plating_jobs/__manifest__.py b/fusion_plating/fusion_plating_jobs/__manifest__.py index f20d9fdd..be3276f2 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.0.0', + 'version': '19.0.1.1.0', 'category': 'Manufacturing/Plating', 'summary': 'Native plating job model — replaces mrp.production / mrp.workorder bridge.', 'description': """ diff --git a/fusion_plating/fusion_plating_jobs/models/__init__.py b/fusion_plating/fusion_plating_jobs/models/__init__.py index 010731a6..a81477b7 100644 --- a/fusion_plating/fusion_plating_jobs/models/__init__.py +++ b/fusion_plating/fusion_plating_jobs/models/__init__.py @@ -3,4 +3,6 @@ # License OPL-1 (Odoo Proprietary License v1.0) # # Phase 2 of the native plating job model migration. Models are added -# task-by-task in Tasks 2.2 onwards. This file imports them as they land. +# task-by-task in Tasks 2.2 onwards. + +from . import fp_job diff --git a/fusion_plating/fusion_plating_jobs/models/fp_job.py b/fusion_plating/fusion_plating_jobs/models/fp_job.py new file mode 100644 index 00000000..9e63e85e --- /dev/null +++ b/fusion_plating/fusion_plating_jobs/models/fp_job.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Nexa Systems Inc. +# License OPL-1 (Odoo Proprietary License v1.0) +# +# fp.job extension — cross-module fields that couldn't live in core +# because their target models are in dependent modules. Per spec §5.1 +# this module is the umbrella that re-bundles the cross-module +# extensions for the native job flow. +# +# qc_check_id is deferred to Task 2.7 (the underlying QC model still +# lives in fusion_plating_bridge_mrp; we'll address its sourcing then). + +from odoo import fields, models + + +class FpJob(models.Model): + _inherit = 'fp.job' + + part_catalog_id = fields.Many2one( + 'fp.part.catalog', + string='Part', + index=True, + ) + coating_config_id = fields.Many2one( + 'fp.coating.config', + string='Coating Configuration', + ) + customer_spec_id = fields.Many2one( + 'fusion.plating.customer.spec', + string='Customer Spec', + ) + portal_job_id = fields.Many2one( + 'fusion.plating.portal.job', + string='Portal Job', + ) + delivery_id = fields.Many2one( + 'fusion.plating.delivery', + string='Delivery', + ) diff --git a/fusion_plating/fusion_plating_jobs/tests/__init__.py b/fusion_plating/fusion_plating_jobs/tests/__init__.py new file mode 100644 index 00000000..0c22091c --- /dev/null +++ b/fusion_plating/fusion_plating_jobs/tests/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import test_fp_job_extensions 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 new file mode 100644 index 00000000..bc0ebbdc --- /dev/null +++ b/fusion_plating/fusion_plating_jobs/tests/test_fp_job_extensions.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +from odoo.tests.common import TransactionCase + + +class TestFpJobExtensions(TransactionCase): + def setUp(self): + super().setUp() + self.partner = self.env['res.partner'].create({'name': 'Cust'}) + self.product = self.env['product.product'].create({'name': 'Widget'}) + + def _make_job(self, **kw): + vals = { + 'partner_id': self.partner.id, + 'product_id': self.product.id, + 'qty': 1.0, + } + vals.update(kw) + return self.env['fp.job'].create(vals) + + def test_part_catalog_id_field_exists(self): + # Field added by fusion_plating_jobs via _inherit. Verify the + # field is registered on the model. + self.assertIn('part_catalog_id', self.env['fp.job']._fields) + self.assertEqual( + self.env['fp.job']._fields['part_catalog_id'].comodel_name, + 'fp.part.catalog', + ) + + def test_coating_config_id_field_exists(self): + self.assertIn('coating_config_id', self.env['fp.job']._fields) + self.assertEqual( + self.env['fp.job']._fields['coating_config_id'].comodel_name, + 'fp.coating.config', + ) + + def test_customer_spec_id_field_exists(self): + self.assertIn('customer_spec_id', self.env['fp.job']._fields) + self.assertEqual( + self.env['fp.job']._fields['customer_spec_id'].comodel_name, + 'fusion.plating.customer.spec', + ) + + def test_portal_job_id_field_exists(self): + self.assertIn('portal_job_id', self.env['fp.job']._fields) + self.assertEqual( + self.env['fp.job']._fields['portal_job_id'].comodel_name, + 'fusion.plating.portal.job', + ) + + def test_delivery_id_field_exists(self): + self.assertIn('delivery_id', self.env['fp.job']._fields) + self.assertEqual( + self.env['fp.job']._fields['delivery_id'].comodel_name, + 'fusion.plating.delivery', + ) + + def test_can_create_job_with_all_fields_set(self): + # End-to-end: create matching records in each target model + # and assign them to a fp.job. Verifies no schema-level issues. + catalog = self.env['fp.part.catalog'].create({ + 'name': 'TestPart', + 'partner_id': self.partner.id, + 'part_number': 'TEST-001', + }) + # fp.coating.config requires a process_type_id + process_type = self.env['fusion.plating.process.type'].search([], limit=1) + if not process_type: + process_type = self.env['fusion.plating.process.type'].create({ + 'name': 'TestProcess', + }) + coating = self.env['fp.coating.config'].create({ + 'name': 'TestCoat', + 'process_type_id': process_type.id, + }) + # fusion.plating.customer.spec requires name + code + spec_type (has default) + spec = self.env['fusion.plating.customer.spec'].create({ + 'name': 'TestSpec', + 'code': 'TEST-SPEC-001', + }) + # fusion.plating.portal.job requires name + partner_id + portal = self.env['fusion.plating.portal.job'].create({ + 'name': 'TestPortal', + 'partner_id': self.partner.id, + }) + # fusion.plating.delivery requires name (has default) + partner_id + delivery = self.env['fusion.plating.delivery'].create({ + 'partner_id': self.partner.id, + }) + job = self._make_job( + part_catalog_id=catalog.id, + coating_config_id=coating.id, + customer_spec_id=spec.id, + portal_job_id=portal.id, + delivery_id=delivery.id, + ) + self.assertEqual(job.part_catalog_id, catalog) + self.assertEqual(job.coating_config_id, coating) + self.assertEqual(job.customer_spec_id, spec) + self.assertEqual(job.portal_job_id, portal) + self.assertEqual(job.delivery_id, delivery)