New module `fusion_service_charges` that creates the standard
service-billing product catalog for Westin Healthcare and Mobility
Specialties:
Standard Service
SVC-STD-CALL Service Call (incl. 30 min) $95
SVC-STD-LABOUR Standard Labour (hourly) $85
SVC-INSHOP-LABOUR In-Shop Labour (hourly) $75
SVC-RUSH-CALL Rush Service Call $120
SVC-AH-CALL After-Hours Service Call $140
Lift & Elevating
SVC-LIFT-CALL Lift Service Call (incl. 30 min) $160
SVC-LIFT-LABOUR Lift Labour (hourly) $110
Delivery / Pickup
DEL-LOCAL Local (within Brampton) $35
DEL-OUT Outside Local Area $60
DEL-RUSH Rush Delivery / Pickup $60
DEL-LIFT-CHAIR Lift Chair Delivery + Set-up $120
DEL-HOSP-BED Hospital Bed Delivery + Set-up $120
DEL-STAIRLIFT Stairlift Delivery + Set-up $300
SVC-STAIRLIFT-RM Stairlift Removal $300
Loading pattern (intentional):
- Products created via post_init_hook on FIRST install only.
- Manifest's `data` list is EMPTY so no XML is loaded on `-u`.
- Hook is idempotent — sentinel ir.model.data xmlid check skips
records that already exist. Safe to re-run.
- User edits / deletes survive every upgrade (proven on entech-
westin: edited SVC-STD-CALL price to $999.99 → ran -u → price
stuck. Reset to $95 after test.).
- Uninstall + reinstall does re-seed (ir.model.data sentinels drop
on uninstall, fresh install treats it as new).
Per-km surcharges (Rush, Outside Local, After-Hours) are noted in
the product description so the dispatcher knows to add a separate
mileage line. Formula-based pricelist for auto-mileage is out of
scope — matches current manual workflow on both shops.
Odoo 19 compatibility: dropped uom_po_id from the create vals
(retired in 18; uom_id is now the single source of truth for sale
and purchase UoM on product.template).
Deployed and verified on:
- odoo-westin / westin-v19 (Docker: odoo-dev-app) — 14 products
- odoo-mobility / mobility (Docker: odoo-mobility-app) — 14 products
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
59 lines
2.1 KiB
Python
59 lines
2.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2026 Nexa Systems Inc.
|
|
# License OPL-1 (Odoo Proprietary License v1.0)
|
|
{
|
|
'name': 'Fusion — Service Charges',
|
|
'version': '19.0.1.0.0',
|
|
'category': 'Sales',
|
|
'summary': (
|
|
'Standard service-call, labour, delivery, and installation '
|
|
'products for Westin Healthcare and Mobility Specialties.'
|
|
),
|
|
'description': """
|
|
Fusion — Service Charges
|
|
==========================
|
|
|
|
Seeds the service-billing product catalog used by Westin Healthcare
|
|
and Mobility Specialties:
|
|
|
|
* Standard Service: Service Call, Labour (hourly), In-Shop Labour,
|
|
Rush Service Call, After-Hours Service Call
|
|
* Lift & Elevating Service: Service Call, Labour (hourly)
|
|
* Delivery / Pickup: Local, Outside Local Area, Rush, Lift Chair
|
|
set-up, Hospital Bed set-up, Stairlift set-up, Stairlift Removal
|
|
|
|
Loading pattern (deliberate):
|
|
|
|
* Products are created via post_init_hook on FIRST install only.
|
|
* No data XML is registered in the manifest, so ``-u`` upgrades
|
|
never touch the records. Edits and deletions made by sales/ops
|
|
survive every upgrade.
|
|
* The hook is idempotent — sentinel xmlid check skips already-
|
|
seeded products. Re-running the hook by hand is safe.
|
|
* Re-installing the module after uninstall re-creates the products
|
|
(the ir.model.data sentinels go away on uninstall, so the next
|
|
install treats it as fresh).
|
|
|
|
Per-km surcharges (Rush, Outside Local) ARE captured on the
|
|
product as a hint in the product description; actual km billing
|
|
is left as a manual SO-line tweak by the dispatcher (matches
|
|
current shop workflow — formula-based pricing would need a
|
|
sale.order.line.onchange to compute, out of scope here).
|
|
""",
|
|
'author': 'Nexa Systems Inc.',
|
|
'website': 'https://www.nexasystems.ca',
|
|
'license': 'OPL-1',
|
|
'depends': [
|
|
'product',
|
|
'uom',
|
|
],
|
|
# Empty on purpose — no data XML. See the docstring on
|
|
# _seed_service_charges_once() for why every product is created
|
|
# imperatively via the post_init_hook.
|
|
'data': [],
|
|
'post_init_hook': 'post_init_hook',
|
|
'installable': True,
|
|
'application': False,
|
|
'auto_install': False,
|
|
}
|