feat(fusion_claims): add fusion.service.rate model + resolvers
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,4 +26,5 @@ from . import ai_agent_ext
|
||||
from . import dashboard
|
||||
from . import res_partner
|
||||
from . import technician_task
|
||||
from . import page11_sign_request
|
||||
from . import page11_sign_request
|
||||
from . import service_rate
|
||||
81
fusion_claims/models/service_rate.py
Normal file
81
fusion_claims/models/service_rate.py
Normal file
@@ -0,0 +1,81 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class FusionServiceRate(models.Model):
|
||||
_name = 'fusion.service.rate'
|
||||
_description = 'Field Service Rate'
|
||||
_order = 'sequence, rate_kind, category, timing'
|
||||
|
||||
name = fields.Char(string='Name', required=True)
|
||||
code = fields.Char(
|
||||
string='Code', required=True, index=True,
|
||||
help='Stable code used by the booking engine, e.g. callout_standard_normal, per_km.',
|
||||
)
|
||||
rate_kind = fields.Selection([
|
||||
('callout', 'Service Call-out'),
|
||||
('labour', 'Labour'),
|
||||
('travel', 'Travel / per-km'),
|
||||
('delivery', 'Delivery / Pickup'),
|
||||
('other', 'Other'),
|
||||
], string='Kind', required=True, default='callout')
|
||||
category = fields.Selection([
|
||||
('standard', 'Standard'),
|
||||
('lift', 'Lift & Elevating'),
|
||||
('na', 'N/A'),
|
||||
], string='Category', default='na')
|
||||
timing = fields.Selection([
|
||||
('normal', 'Normal'),
|
||||
('rush', 'Rush'),
|
||||
('afterhours', 'After-Hours'),
|
||||
('na', 'N/A'),
|
||||
], string='Timing', default='na')
|
||||
in_shop = fields.Boolean(string='In-Shop')
|
||||
product_id = fields.Many2one(
|
||||
'product.product', string='Invoice Product', required=True, ondelete='restrict',
|
||||
help='Product used on the sale-order line (description, tax, income account).',
|
||||
)
|
||||
price = fields.Monetary(
|
||||
string='Rate', required=True, currency_field='currency_id',
|
||||
help='Editable price used on the SO line and the on-screen estimate.',
|
||||
)
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency', string='Currency',
|
||||
default=lambda self: self.env.company.currency_id,
|
||||
)
|
||||
unit = fields.Selection([
|
||||
('fixed', 'Flat'),
|
||||
('per_hour', 'Per hour'),
|
||||
('per_km', 'Per km'),
|
||||
], string='Unit', required=True, default='fixed')
|
||||
adds_per_km = fields.Boolean(
|
||||
string='Adds per-km travel',
|
||||
help='Call-outs billed as $X + per-km \xd7 2-way (rush / after-hours).',
|
||||
)
|
||||
included_labour_min = fields.Integer(
|
||||
string='Included labour (min)', default=0,
|
||||
help='Free labour minutes bundled into a service call (e.g. 30).',
|
||||
)
|
||||
active = fields.Boolean(string='Active', default=True)
|
||||
sequence = fields.Integer(string='Sequence', default=10)
|
||||
|
||||
_unique_code = models.Constraint(
|
||||
'UNIQUE(code)',
|
||||
'A service-rate code must be unique.',
|
||||
)
|
||||
|
||||
@api.model
|
||||
def get_callout(self, category, timing, in_shop=False):
|
||||
"""Active call-out rate for category+timing. Empty recordset when in-shop."""
|
||||
if in_shop:
|
||||
return self.browse()
|
||||
return self.search([
|
||||
('rate_kind', '=', 'callout'),
|
||||
('category', '=', category),
|
||||
('timing', '=', timing),
|
||||
], limit=1)
|
||||
|
||||
@api.model
|
||||
def get_rate(self, code):
|
||||
"""Active rate row by code (e.g. 'per_km', 'labour_onsite')."""
|
||||
return self.search([('code', '=', code)], limit=1)
|
||||
@@ -3,3 +3,4 @@
|
||||
from . import test_signed_pages_gate
|
||||
from . import test_application_received_wizard
|
||||
from . import test_dashboard
|
||||
from . import test_service_rate
|
||||
|
||||
55
fusion_claims/tests/test_service_rate.py
Normal file
55
fusion_claims/tests/test_service_rate.py
Normal file
@@ -0,0 +1,55 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo.tests.common import TransactionCase, tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestServiceRate(TransactionCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.Rate = cls.env['fusion.service.rate']
|
||||
cls.product = cls.env['product.product'].create({
|
||||
'name': 'Test Service Product', 'type': 'service',
|
||||
})
|
||||
|
||||
def _make(self, **kw):
|
||||
vals = dict(name='Rate', code='c1', rate_kind='callout', category='standard',
|
||||
timing='normal', product_id=self.product.id, price=95.0, unit='fixed')
|
||||
vals.update(kw)
|
||||
return self.Rate.create(vals)
|
||||
|
||||
def test_get_callout_matches_category_and_timing(self):
|
||||
r = self._make(code='callout_standard_normal', category='standard', timing='normal', price=95.0)
|
||||
self._make(code='callout_lift_normal', category='lift', timing='normal', price=160.0)
|
||||
self.assertEqual(self.Rate.get_callout('standard', 'normal'), r)
|
||||
|
||||
def test_get_callout_in_shop_returns_empty(self):
|
||||
self._make(code='callout_standard_normal_b')
|
||||
self.assertFalse(self.Rate.get_callout('standard', 'normal', in_shop=True))
|
||||
|
||||
def test_get_rate_by_code(self):
|
||||
r = self._make(code='per_km', rate_kind='travel', category='na', timing='na', unit='per_km', price=0.70)
|
||||
self.assertEqual(self.Rate.get_rate('per_km'), r)
|
||||
|
||||
def test_code_must_be_unique(self):
|
||||
self._make(code='dup')
|
||||
with self.assertRaises(Exception):
|
||||
self._make(code='dup')
|
||||
self.env.flush_all()
|
||||
|
||||
def test_seeded_callouts_exist(self):
|
||||
# standard normal $95, lift after-hours $205 are the canonical seeds
|
||||
std = self.env.ref('fusion_claims.rate_callout_standard_normal')
|
||||
self.assertEqual(std.price, 95.0)
|
||||
self.assertEqual(std.rate_kind, 'callout')
|
||||
self.assertTrue(std.product_id)
|
||||
lift_ah = self.env.ref('fusion_claims.rate_callout_lift_afterhours')
|
||||
self.assertEqual(lift_ah.price, 205.0)
|
||||
self.assertTrue(lift_ah.adds_per_km)
|
||||
|
||||
def test_seeded_per_km(self):
|
||||
km = self.env['fusion.service.rate'].get_rate('per_km')
|
||||
self.assertTrue(km)
|
||||
self.assertEqual(km.unit, 'per_km')
|
||||
self.assertEqual(km.price, 0.70)
|
||||
Reference in New Issue
Block a user