ADR 0011: Product Lifecycle — From Global Product to Local Retail¶
Status¶
Proposed
Date¶
2026-07-31
Context¶
dirac-odoo already defines:
- V3 association model (ADR 0003): SA governance via explicit edge objects. No direct stamping on native Odoo models.
- Fleet and ITEM governance (ADR 0004):
stock.lotas the single point of truth for device identity.ov.fleetfor operational grouping independent of physical location. - Company Product Profile Framework (ADR 0009): Three-layer model (Body + Profile + SA governance). Profiles carry localization content only — no pricing, no cost, no inventory config.
- PoS-PA Process (ADR 0010): End-to-end retail workflow under SA governance.
Two critical architectural flaws were discovered during implementation review:
Flaw 1 — Tracking Is Template-Level, Not Company-Dependent¶
product.template.tracking is a global field. A product tracked as "lot" in one company cannot be tracked as "serial" in another. ADR 0010 assumed serialization could happen at PoS inbound from lot-tracked warehouse stock. This is architecturally wrong:
- Odoo blocks tracking changes while stock exists
- Serial labels are physically on products from the factory — Odoo is the only system ignoring them
- No serial identity exists for fleet, warranty, or Thing federation before retail
Flaw 2 — Body Immutability Claim Is Incorrect¶
ADR 0009 claimed the Body (product.template) is immutable engineering truth. But tracking is a Body-level field that legitimately varies by operational context. The Body is not as immutable as stated.
Flaw 3 — ADR 0010 Section 5 Assumed Serialization at PoS¶
ADR 0010 Section 5 and Section 8 described a lot-to-serial conversion during the warehouse-to-PoS stock move. This is not possible without zeroing all stock globally, which is operationally impractical for products sold in bulk (B2B) and retail (B2C) simultaneously.
Business Reality¶
OVES sells the same physical product to: - B2B customers: bulk orders (1,000+ units), lot-tracking sufficient - Retail customers: individual units via SA-PoS, serial-tracking required
Forcing all products to serial-tracking wastes effort on the 99% of units sold B2B. Forcing all to lot-tracking prevents retail traceability, fleet management, and warranty tracking.
Decision¶
1. Lot Tracking Is the Default. Serial Is a Transformation.¶
Default: All new products are created with tracking = "lot".
Serialization: When a product needs individual traceability (retail, fleet, warranty, IoT), it is transformed via a Manufacturing Order:
BoM: L190-SERIAL
Components:
1 × L190-LOT (lot-tracked, consumed from inventory)
1 × LABEL-190 (physical serial label, consumable)
Operations:
"Serialization — Label Application and Scan"
This replaces the ADR 0010 Section 5 assumption of serialization-at-PoS-inbound.
Supersedes: ADR 0010 Section 5, Section 8. ADR 0009 "no duplication" rule — tracking divergence is a documented exception.
2. Serial Identity Exists from MO Completion¶
The serial (stock.lot) for the SERIAL product is created at MO completion — not at PoS, not at retail activation. From that moment:
ov.fleet_itemcan reference it (ADR 0004)ov.thing_linkcan bind to it (ADR 0004 D14)ov.sa_pos_ordercan record it at retail (ADR 0010)
No more data gap between factory and retail.
3. Cost Is AVCO, Company-Dependent, Landed-Cost-Aware¶
product.product.standard_price is a company-dependent field. Each company in the multi-company group has its own cost for the same product template.
Important Note for Teams: standard_price is misnamed — it is inventory unit cost, not selling price. Selling prices live in product.pricelist.item.
| Cost Component | Odoo Mechanism |
|---|---|
| Factory / supplier cost | PO receipt. AVCO auto-recalculated. |
| Per-shipment costs (freight, duty, port, local transport) | stock.landed.cost — one document per shipment, cost lines per cost type, each to its own expense account |
| VAT (recoverable) | Posted to VAT deductible account. Does NOT increase inventory cost. |
| VAT (non-recoverable) | Added to landed cost lines that increase inventory cost. |
Costing Method: AVCO (Average Cost). Replaces standard costing for products with volatile per-shipment procurement.
| Standard Costing | AVCO |
|---|---|
| Manually updated by finance | Auto-calculated per receipt + landed cost |
| PPV accumulates per-shipment noise | Cost reflects actual procurement reality |
| Prone to staleness | Always current |
standard_price Is Company-Dependent. OVK, OVT, OVHM each have their own cost for the same product. The template view shows the active company's value. Do not edit standard_price directly under AVCO — it is auto-calculated. Landed cost allocation flows to valuation layers, which AVCO uses for recalculation.
4. Pricing Pipeline: AVCO → Working Pricelist → Static Retail Pricelist¶
Pricelists do NOT auto-update from cost changes. Intentional decoupling:
AVCO cost (auto)
│
▼
Working Pricelist "Kenya Retail — Working"
│ Formula items: cost × 1.4
│ Visible to: Commercial team only
│
▼ Commercial reviews, copies, adjusts
│
Static Pricelist "Kenya Retail Q3 2026"
│ Fixed price items (snapshotted from Working)
│ Visible to: Customers, SA-PoS
│ Stable until commercial publishes new version
| Step | Who | When |
|---|---|---|
| Cost changes (AVCO recalculation) | Odoo | Per receipt + landed cost |
| Review Working Pricelist | Commercial team | Weekly, or on cost threshold alert |
| Copy to Static Pricelist | Commercial manager | When review is complete and prices are approved |
| Activate new Static Pricelist | Commercial manager | New date_start, old date_end |
Important Note for Teams: A "Working" pricelist with formula items recomputes at quotation time. A "Static" pricelist with fixed prices does not. Use Working for cost visibility. Use Static for customer-facing stability.
5. Product Profile Is Independent and Parallel¶
Profiles (ADR 0009) carry only localization content: local name, local description, marketing text, regulatory notes, promotion eligibility. Profiles do NOT carry pricing, cost, or inventory configuration.
A SERIAL product gets its own Profile. An SA-PoS config associates to that Profile. The LOT product is never profiled — no retail visibility.
| Pipeline | Owner | Independent? |
|---|---|---|
| Profile (content) | Operations / Marketing | ✅ |
| Cost → AVCO | Procurement / Odoo | ✅ |
| Working → Static Pricelist | Commercial | ✅ |
| Lot → Serial BOM | Production / Warehouse | ✅ |
6. End-to-End Workflow: Factory to Retail¶
FACTORY
L190-LOT produced (tracking=lot)
AVCO cost calculated at factory company
INTERNATIONAL SHIPMENT
PO receipt at country warehouse
Landed cost applied (freight, duty, port, local transport)
AVCO recalculated at receiving company
SERIALIZATION (when needed for retail)
MO: L190-LOT + LABEL → L190-SERIAL
Serial created at MO completion
Cost inherited from LOT → SERIAL via MO (standard_price = BoM cost)
Thing federation: ov.thing_link now bindable
Fleet: ov.fleet_item now referenceable
PROFILING (independent, parallel)
Profile created for L190-SERIAL
SA-assigned profile via ov.sa_product_profile
Local name, description, marketing text applied
PRICING (independent, parallel)
Working pricelist updated from current AVCO cost
Commercial reviews, approves
Static pricelist activated for customers and SA-PoS
RETAIL (SA-PoS per ADR 0010)
SA-PoS config assigned to serial product's profile
Clerk scans serial at checkout
ov.sa_pos_order + ov.actor_sa_pos_order created
Stock move: serial leaves retail location → customer
Superseded Sections¶
| ADR | Section | Superseded By | Reason |
|---|---|---|---|
| 0009 | Decision D1 (one Body immutable) | ADR 0011 D1 | tracking varies by operational context. LOT+SERIAL products share engineering BOM but differ in tracking. |
| 0010 | Section 5 (serialized inventory, per-location) | ADR 0011 D1-D2 | Serialization is a manufacturing step (MO), not a logistics step (stock move). Serial exists before PoS. |
| 0010 | Section 8 (warehouse → PoS internal transfer with serialization) | ADR 0011 D1-D2 | Lot→serial conversion during stock move is not possible. Serialized product arrives at PoS already serialized. |
Consequences¶
- Lot tracking is the default for all new products. Serial is created via BOM when needed.
standard_priceis company-dependent and auto-managed under AVCO. No manual updates.- Landed costs are per-shipment, per-cost-type, each to its own expense account.
- Selling prices use a dual-pricelist pipeline: Working (formula, commercial-only) → Static (fixed, customer-facing).
- Profiles remain independent of cost and tracking — pure content layer.
- ADR 0009 and ADR 0010 Sections 5/8 are superseded. Their original text remains as decision history.
Cross-References¶
- ADR 0003 (V3 association pattern): All SA governance governed via explicit edge objects.
- ADR 0004 (Fleet and ITEM governance): SERIAL product's
stock.lotis the ITEM identity for fleet, warranty, and Thing federation. - ADR 0009 (Product Profile Framework): Profiles remain as defined — content only, no pricing/cost/inventory.
- ADR 0010 (PoS-PA Process): Retail workflow unchanged except Sections 5/8.
- ADR 0008 (Assembly-Cell): No change. Assembly produces FG serials per its own workflow. If the FG product needs lot tracking for B2B and serial for retail, the LOT→SERIAL BOM applies after assembly.