Skip to content

ADR 0011: Product Lifecycle — From Global Product to Local Retail

Status

Proposed

Date

2026-07-31


Context

dirac-odoo already defines:

  1. V3 association model (ADR 0003): SA governance via explicit edge objects. No direct stamping on native Odoo models.
  2. Fleet and ITEM governance (ADR 0004): stock.lot as the single point of truth for device identity. ov.fleet for operational grouping independent of physical location.
  3. 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.
  4. 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_item can reference it (ADR 0004)
  • ov.thing_link can bind to it (ADR 0004 D14)
  • ov.sa_pos_order can 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_price is 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.lot is 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.