feat(configurator): per-part description version model + part load/save helpers

fp.part.description.version: immutable per-part snapshots with version_no/
is_latest maintained in create(), titled "<SO#> · <date>". fp.part.catalog
gains description_version_ids + _fp_resolve_line_descriptions (load latest,
fallback to default_specification_text) and _fp_save_description_version
(dedup + sync default). ACL mirrors fp.sale.description.template.

Tests deferred to entech (local Docker unavailable this session).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-29 19:55:14 -04:00
parent 9b18f77e06
commit 2ed3dcee58
7 changed files with 215 additions and 1 deletions

View File

@@ -7,3 +7,4 @@ from . import test_express_line_fields
from . import test_express_so_line_fields
from . import test_express_sale_order_fields
from . import test_express_wizard_fields
from . import test_part_description_history

View File

@@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
"""Per-part description history (spec 2026-05-29)."""
from odoo.tests.common import TransactionCase, tagged
@tagged('post_install', '-at_install', 'fp_desc_history')
class TestPartDescriptionHistory(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.partner = cls.env['res.partner'].create({'name': 'DescCust'})
cls.part = cls.env['fp.part.catalog'].create({
'partner_id': cls.partner.id,
'part_number': 'DH-001',
'revision': 'A',
'name': 'Desc Part',
})
def _mk_version(self, internal, customer, **kw):
vals = {
'part_catalog_id': self.part.id,
'internal_description': internal,
'customer_facing_description': customer,
}
vals.update(kw)
return self.env['fp.part.description.version'].create(vals)
# ----- Task 1: model invariants -----
def test_version_no_increments_and_is_latest_flips(self):
v1 = self._mk_version('int 1', 'cust 1')
v2 = self._mk_version('int 2', 'cust 2')
self.assertEqual(v1.version_no, 1)
self.assertEqual(v2.version_no, 2)
self.assertFalse(v1.is_latest)
self.assertTrue(v2.is_latest)
def test_name_uses_order_and_date(self):
so = self.env['sale.order'].create({'partner_id': self.partner.id})
v = self._mk_version('i', 'c', sale_order_id=so.id,
source_date='2026-05-29')
self.assertIn(so.name, v.name)
self.assertIn('2026-05-29', v.name)
# ----- Task 2: part helpers -----
def test_resolve_falls_back_to_default_spec(self):
self.part.default_specification_text = 'legacy cust'
descs = self.part._fp_resolve_line_descriptions()
self.assertEqual(descs['customer_facing'], 'legacy cust')
self.assertEqual(descs['internal'], '')
def test_resolve_prefers_latest_version(self):
self.part.default_specification_text = 'legacy cust'
self._mk_version('hist int', 'hist cust')
descs = self.part._fp_resolve_line_descriptions()
self.assertEqual(descs['customer_facing'], 'hist cust')
self.assertEqual(descs['internal'], 'hist int')
def test_save_dedups_when_unchanged(self):
self.part._fp_save_description_version('i', 'c')
self.part._fp_save_description_version('i', 'c') # identical
self.assertEqual(
self.env['fp.part.description.version'].search_count(
[('part_catalog_id', '=', self.part.id)]), 1)
def test_save_creates_new_on_change_and_syncs_default(self):
self.part._fp_save_description_version('i1', 'c1')
self.part._fp_save_description_version('i1', 'c2') # changed
versions = self.env['fp.part.description.version'].search(
[('part_catalog_id', '=', self.part.id)])
self.assertEqual(len(versions), 2)
self.assertEqual(self.part.default_specification_text, 'c2')