feat(fusion_repairs): maintenance foundation - policy + priced auto-contracts on sale (Plan 1)
Plan 1 of fusion_maintenance, verified on the Westin Enterprise sandbox (westin-fr-test) via odoo shell. Maintenance policy (enabled/interval/flat fee/service product) on the equipment category + per-product fee override; contract gains fee/source/serial/policy/currency; fixed the dead _spawn_maintenance_contracts and wired it into the existing action_confirm (delivery-date anchor w/ fallback, two-regime serial dedup, fee resolution product->category); reminder email shows the flat fee; category form exposes the policy. Verified: trigger creates 1 priced contract (fee 149, next_due commitment+6mo, source=sale); idempotent on re-confirm; product override beats category; no contract when category not maintainable; fee renders as $149.00. v19.0.2.3.0. NOTE: mail_template_data.xml is noupdate=1 -> the fee line loads on fresh install (the prod deploy) but NOT on -u of an already-installed system. The Westin prod-config test container (workers + log_level=warn) does not run --test-enable post_install tests (a pre-existing module load issue under the test phase), so behaviour was verified by odoo shell instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2
fusion_repairs/tests/__init__.py
Normal file
2
fusion_repairs/tests/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import test_maintenance_foundation
|
||||
100
fusion_repairs/tests/test_maintenance_foundation.py
Normal file
100
fusion_repairs/tests/test_maintenance_foundation.py
Normal file
@@ -0,0 +1,100 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo.tests import TransactionCase, tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestMaintenanceFoundation(TransactionCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.partner = cls.env['res.partner'].create({'name': 'Mrs. Test Client'})
|
||||
cls.category = cls.env['fusion.repair.product.category'].create({
|
||||
'name': 'Stair Lift', 'code': 'stairlift_test',
|
||||
'equipment_class': 'lift_elevating', 'safety_critical': True,
|
||||
'x_fc_maintenance_enabled': True,
|
||||
'x_fc_maintenance_interval_months': 6,
|
||||
'x_fc_maintenance_fee': 149.0,
|
||||
})
|
||||
|
||||
# ---- Tasks 1/2/3: fields exist ----
|
||||
def test_category_policy_fields(self):
|
||||
self.assertTrue(self.category.x_fc_maintenance_enabled)
|
||||
self.assertEqual(self.category.x_fc_maintenance_interval_months, 6)
|
||||
self.assertEqual(self.category.x_fc_maintenance_fee, 149.0)
|
||||
self.assertTrue(self.category.currency_id)
|
||||
|
||||
def test_product_fee_override_field(self):
|
||||
tmpl = self.env['product.template'].create({
|
||||
'name': 'Handicare Freecurve Stairlift',
|
||||
'x_fc_repair_category_id': self.category.id,
|
||||
'x_fc_maintenance_fee': 199.0,
|
||||
})
|
||||
self.assertEqual(tmpl.x_fc_maintenance_fee, 199.0)
|
||||
|
||||
def test_contract_extension_fields(self):
|
||||
product = self.env['product.product'].create({'name': 'Unit'})
|
||||
c = self.env['fusion.repair.maintenance.contract'].create({
|
||||
'partner_id': self.partner.id,
|
||||
'product_id': product.id,
|
||||
'next_due_date': '2026-12-01',
|
||||
'x_fc_source': 'sale',
|
||||
'x_fc_device_serial': 'SN-123',
|
||||
'x_fc_maintenance_fee': 149.0,
|
||||
})
|
||||
self.assertEqual(c.x_fc_source, 'sale')
|
||||
self.assertEqual(c.x_fc_device_serial, 'SN-123')
|
||||
self.assertEqual(c.x_fc_maintenance_fee, 149.0)
|
||||
|
||||
# ---- Task 4: spawn priced contracts on sale confirm ----
|
||||
def _make_product(self, **kw):
|
||||
vals = {'name': 'Stairlift Unit', 'type': 'consu',
|
||||
'x_fc_repair_category_id': self.category.id}
|
||||
vals.update(kw)
|
||||
return self.env['product.product'].create(vals)
|
||||
|
||||
def _confirm_so(self, product, commitment='2026-01-10'):
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': self.partner.id,
|
||||
'commitment_date': commitment,
|
||||
'order_line': [(0, 0, {'product_id': product.id, 'product_uom_qty': 1})],
|
||||
})
|
||||
so.action_confirm()
|
||||
return so
|
||||
|
||||
def _contracts_for(self, so):
|
||||
return self.env['fusion.repair.maintenance.contract'].search(
|
||||
[('original_sale_order_id', '=', so.id)])
|
||||
|
||||
def test_no_contract_when_category_not_maintainable(self):
|
||||
cat = self.env['fusion.repair.product.category'].create(
|
||||
{'name': 'Cane', 'code': 'cane_test', 'x_fc_maintenance_enabled': False})
|
||||
so = self._confirm_so(self._make_product(x_fc_repair_category_id=cat.id))
|
||||
self.assertFalse(self._contracts_for(so))
|
||||
|
||||
def test_contract_created_via_category_policy(self):
|
||||
so = self._confirm_so(self._make_product())
|
||||
contracts = self._contracts_for(so)
|
||||
self.assertEqual(len(contracts), 1)
|
||||
c = contracts
|
||||
self.assertEqual(c.interval_months, 6)
|
||||
self.assertEqual(c.x_fc_maintenance_fee, 149.0)
|
||||
self.assertEqual(c.x_fc_source, 'sale')
|
||||
self.assertEqual(c.x_fc_policy_category_id, self.category)
|
||||
# anchor = commitment_date (2026-01-10) + 6 months
|
||||
self.assertEqual(str(c.next_due_date), '2026-07-10')
|
||||
|
||||
def test_product_override_beats_category(self):
|
||||
p = self._make_product()
|
||||
p.product_tmpl_id.x_fc_maintenance_interval_months = 3
|
||||
p.product_tmpl_id.x_fc_maintenance_fee = 199.0
|
||||
so = self._confirm_so(p)
|
||||
c = self._contracts_for(so)
|
||||
self.assertEqual(c.interval_months, 3)
|
||||
self.assertEqual(c.x_fc_maintenance_fee, 199.0)
|
||||
|
||||
def test_idempotent_on_reconfirm(self):
|
||||
p = self._make_product()
|
||||
so = self._confirm_so(p)
|
||||
so._spawn_maintenance_contracts() # call again -> no duplicate
|
||||
self.assertEqual(len(self._contracts_for(so)), 1)
|
||||
Reference in New Issue
Block a user