From 855b1607525cc71e0e059b4875653268a4578975 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Fri, 29 May 2026 19:57:16 -0400 Subject: [PATCH] feat(configurator): auto-load latest part description version on order entry Wizard line (direct + express) and SO line now pre-fill BOTH internal + customer-facing from the part's latest version (fallback to default_specification_text), without clobbering typed text. Co-Authored-By: Claude Opus 4.7 --- .../models/sale_order_line.py | 14 ++++++++++++++ .../tests/test_part_description_history.py | 10 ++++++++++ .../wizard/fp_direct_order_line.py | 16 ++++++++++------ 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/fusion_plating/fusion_plating_configurator/models/sale_order_line.py b/fusion_plating/fusion_plating_configurator/models/sale_order_line.py index 31a59807..f641c47e 100644 --- a/fusion_plating/fusion_plating_configurator/models/sale_order_line.py +++ b/fusion_plating/fusion_plating_configurator/models/sale_order_line.py @@ -42,6 +42,20 @@ class SaleOrderLine(models.Model): return tail.lstrip(' \t\r\n-—–:').strip() return name + @api.onchange('x_fc_part_catalog_id') + def _fp_onchange_part_load_description(self): + """Pre-fill name (customer-facing) + x_fc_internal_description from + the part's latest description version, when empty (spec 2026-05-29).""" + for line in self: + part = line.x_fc_part_catalog_id + if not part: + continue + descs = part._fp_resolve_line_descriptions() + if not line.name and descs['customer_facing']: + line.name = descs['customer_facing'] + if not line.x_fc_internal_description and descs['internal']: + line.x_fc_internal_description = descs['internal'] + x_fc_part_catalog_id = fields.Many2one( 'fp.part.catalog', string='Part', ) diff --git a/fusion_plating/fusion_plating_configurator/tests/test_part_description_history.py b/fusion_plating/fusion_plating_configurator/tests/test_part_description_history.py index 949c45d3..ada0f931 100644 --- a/fusion_plating/fusion_plating_configurator/tests/test_part_description_history.py +++ b/fusion_plating/fusion_plating_configurator/tests/test_part_description_history.py @@ -72,3 +72,13 @@ class TestPartDescriptionHistory(TransactionCase): [('part_catalog_id', '=', self.part.id)]) self.assertEqual(len(versions), 2) self.assertEqual(self.part.default_specification_text, 'c2') + + # ----- Task 5: SO line auto-load ----- + def test_so_line_onchange_loads_latest_version(self): + self.part._fp_save_description_version('shop notes', 'cust spec') + line = self.env['sale.order.line'].new({ + 'x_fc_part_catalog_id': self.part.id, + }) + line._fp_onchange_part_load_description() + self.assertEqual(line.name, 'cust spec') + self.assertEqual(line.x_fc_internal_description, 'shop notes') diff --git a/fusion_plating/fusion_plating_configurator/wizard/fp_direct_order_line.py b/fusion_plating/fusion_plating_configurator/wizard/fp_direct_order_line.py index b6f3ec6e..b59e63f4 100644 --- a/fusion_plating/fusion_plating_configurator/wizard/fp_direct_order_line.py +++ b/fusion_plating/fusion_plating_configurator/wizard/fp_direct_order_line.py @@ -629,12 +629,16 @@ class FpDirectOrderLine(models.Model): if bake_default: rec.bake_instructions = bake_default - # ---- Express line_description (Specification) ---- - # Only seed if currently empty (don't clobber operator-typed text). - if not rec.line_description: - spec_default = getattr(part, 'default_specification_text', None) - if spec_default: - rec.line_description = spec_default + # ---- Description history auto-load (spec 2026-05-29) ---- + # Latest per-part version wins; _fp_resolve_line_descriptions + # falls back to default_specification_text (customer-facing only) + # for parts with no version yet. Loads BOTH fields; never + # clobbers text the operator already typed. + descs = part._fp_resolve_line_descriptions() + if not rec.line_description and descs['customer_facing']: + rec.line_description = descs['customer_facing'] + if not rec.internal_description and descs['internal']: + rec.internal_description = descs['internal'] # ---- Express masking_enabled (always re-seed from part default) ---- # Boolean has no "empty" state; explicit reseed on every part-change