ADR 0008: Assembly-Cell Process with SA Governance¶
Status¶
Proposed
Date¶
2026-06-18
Context¶
dirac-odoo already defines:
- SA governance model (ADR 0005): SAs govern POS transactions, with agents (cashiers), SA managers, and three-tier reporting.
- Fleet/ITEM model (ADR 0004):
ov.fleet,ov.fleet_item, and native Odoostock.production.lot(serialized ITEM). - Asset registry (ADR 0007):
ov.asset.registryandov.asset.componentcreated at MO completion. - Build Record (
asset-lifecycle/docs/9-warranty/2-build-record-and-asset-passport.md): the as-built document capturing component identities at assembly.
The assembly process for LEV production uses CKD boxes (lot-tracked in Odoo). Each CKD box = one MO = one vehicle. The Assembly-Cell applet (a PA applet, NOT Odoo PWA) runs on desktop or tablet, and guides the assembler through capturing component serials (which exist as physical labels but are lot-tracked, not serialized, in Odoo).
This ADR defines: - The Assembly-Cell applet workflow (PA applet, form-based) - SA governance for assembly cells (SA = Production Location, may contain cells and/or lines; cell/line operators = actors) - Build Record lifecycle (draft at form open, finalized at manager sign-off) - Serial + OEM-ID creation (at manager sign-off, when physical assembly is confirmed)
Decision¶
Adopt the SA governance model for Assembly-Cell processes.
- Assembly-Cell applet: a PA applet (physical desktop + tablet), NOT Odoo PWA. Form-based: assembler scans CKD manifest → form opens → progressively scans components → submits.
- CKD box = one MO = one vehicle. No batch MOs for assembly. Each vehicle gets its own MO, linked to one CKD box and one assembly cell.
- SA = Production Location (may contain cells and/or lines). Cell and Line are different production processes, each needing different SA applets. Cell operators and line operators are actors in that SA. The cell/line itself is not an SA.
- Build Record lifecycle: draft created when form opens (CKD manifest scanned); finalized when line manager signs off the MO (physical assembly confirmed).
- Serial + OEM-ID creation: at line manager sign-off (not before), Odoo creates
stock.production.lot(Serial) and assigns OEM-ID. Build Record is then linked to the Serial.
This ADR defines the Odoo model extensions and the Assembly-Cell PA applet workflow. The domain/business process is documented in asset-lifecycle repo (cross-reference added when that doc is created).
1. Assembly-Cell Applet — PA Applet, Not Odoo PWA¶
Design Principle¶
Assembly-Cell is a PA applet (physical desktop + tablet), NOT an Odoo PWA. It follows the same pattern as the Sales applet and Swap Operations applet. All access is governed by PA/SA.
Assembly-Cell Applet Workflow¶
| Step | Action | Odoo Transaction |
|---|---|---|
| 1. CKD box delivered to cell | Worker scans CKD packing manifest (barcode/QR) | Opens the linked MO in the applet |
| 2. Open CKD box | Worker scans components one by one: chassis, MCU, motor, VCU | Component serials captured in Assembly-Cell form |
| 3. Progressive data entry | Worker fills in all required fields in the form | Data stored as draft Build Record |
| 4. Physical assembly | Worker assembles the LEV | No Odoo transaction yet (cannot guarantee parts are actually assembled) |
| 5. Line manager inspects | Manager checks the finished product | Manager signs the MO in the applet |
| 6. Manager signs off | APPROVES the MO | Odoo: creates stock.production.lot (Serial), assigns OEM-ID |
| 7. Build Record finalized | Links Serial + OEM-ID to component data | ov.asset.registry record finalized |
Build Record Lifecycle¶
| Stage | When | What Happens |
|---|---|---|
| Draft | CKD manifest scanned, form opens | Build Record created in ov.asset.registry (draft status) |
| Progressively updated | Worker scans each component | Component serials added to the Build Record |
| Finalized | Line manager signs off MO | Serial created, OEM-ID assigned, Build Record linked to Serial |
Component Serials — Current State¶
- In Odoo: components are lot-tracked (not serialized). They exist as
stock.production.lot(LOT), not as Serials. - Physically: each part has a paper ID / QR code printed on it.
- In Build Record: component serials are captured as text fields in
ov.asset.component(part ofov.asset.registry).
Future consideration (not now): if we need to trace a component to its OEM directly (not just via the CKD box), then box-open should create Odoo Serials for components. This is a future deliberation item.
2. Assembly Generates NEW Item — CKD Box = One MO = One Vehicle¶
Design Principle¶
One CKD box = one MO = one vehicle. No batch MOs for assembly. Each vehicle gets its own MO, linked to one CKD box and one assembly cell. If Odoo natively supports batch MOs for other processes, that is separate — the assembly MO is always 1:1 with a vehicle.
Workflow: From CKD Box to Finished Vehicle¶
| Step | Physical Action | Odoo Transaction | Build Record Status |
|---|---|---|---|
| 1. CKD box delivered | CKD box delivered to assembly cell | stock.move: OVT/CKD Materials → OVT/Assembly |
— |
| 2. Scan CKD manifest | Worker scans barcode/QR on CKD packing manifest | Assembly-Cell applet opens the linked MO form | Draft (form opened) |
| 3. Open CKD box, scan components | Worker scans chassis, MCU, motor, VCU serials (physical labels) | Component serials captured in form (text fields in ov.asset.component) |
Draft (progressively updated) |
| 4. Physical assembly | Worker assembles the LEV | No Odoo transaction yet (cannot guarantee parts are actually assembled) | Draft (form can be updated) |
| 5. Line manager inspects | Manager checks the finished product | No Odoo transaction yet | Draft (awaiting sign-off) |
| 6. Manager signs off | Manager approves the MO in the applet | mrp.production done → stock.lot (Serial) created → OEM-ID assigned |
Finalized (linked to Serial + OEM-ID) |
| 7. FG put away | FG moves to finished goods | stock.move: OVT/Assembly → OVT/Finished Goods |
Finalized |
Build Record Lifecycle¶
The Build Record (ov.asset.registry row) has two states:
| State | When | What is recorded |
|---|---|---|
| Draft | CKD manifest scanned, form opens | mo_id, sa_id, actor_id, ckd_lot_id, timestamp |
| Draft (progressively updated) | Worker scans each component | chassis_serial, mcu_serial, motor_serial, vcu_serial added to ov.asset.component rows |
| Finalized | Line manager signs off MO | stock.production.lot (Serial) created, OEM-ID assigned, Build Record linked to Serial |
Component Serials — Current State¶
- In Odoo: components are lot-tracked (not serialized). They exist as
stock.production.lot(LOT), not as Serials. - Physically: each part has a paper ID / QR code printed on it.
- In Build Record: component serials are captured as text fields in
ov.asset.component(part ofov.asset.registry).
Future consideration (not now): if we need to trace a component to its OEM directly (not just via the CKD box), then box-open should create Odoo Serials for components. This is a future deliberation item.
BoM Consumption¶
The BoM (mrp.bom) defines:
- CKD package (lot-tracked input)
- FG vehicle (serial-tracked output)
BoM consumption happens at MO completion (when manager signs off). The CKD lot is consumed, and the FG serial is produced. Component serials are not Odoo Serials — they are captured in the Build Record.
3. Model at ITEM Level — FLEET, ITEM, Odoo Serial¶
Design Principle¶
Asset tracking is modeled at ITEM level (serialized asset).
ov.fleet,ov.fleet_item, and Odoostock.production.lot(serial) are the basis.
ITEM Identity Stack¶
| Layer | Model | Description |
|---|---|---|
| Conceptual | asset-lifecycle repo |
Lifecycle rules, warranty scoping |
| Registry | ov.asset.registry |
Stable asset identity anchor (Build Record) |
| Component | ov.asset.component |
Component lineage and replacement history |
| ITEM | stock.production.lot (serial) |
Odoo-native serialized ITEM |
| Fleet | ov.fleet + ov.fleet_item |
Operational grouping (survives location change) |
| SA Governance | ov.sa_fleet + ov.actor_sa_fleet |
Which SA governs which fleet/ITEM |
| Build Record Lifecycle | ov.asset.registry.status |
Draft → Finalized (at manager sign-off) |
How ITEM Enters SA Governance¶
After assembly (MO completion + ov.asset.registry creation), the ITEM (FG serial) may be:
- Assigned to a fleet (
ov.fleet_item) - Assigned to an SA (
ov.sa_fleet) - Transferred to sales (stock move to retail location)
- Sold to customer (POS transaction, SA-governed via
ov.sa_pos_order)
The SA governance chain:
SA (Production Location) → ov.sa_fleet → ov.fleet → ov.fleet_item → stock.production.lot (ITEM)
This is the same pattern as POS:
- POS: SA → ov.sa_pos_order → ov.sa_pos_order → pos.order
- Assembly: SA → ov.sa_fleet → ov.fleet → ov.fleet_item → stock.production.lot
4. SA Governance for Assembly-Cell (SA = Production Location)¶
Design Principle¶
SA = Production Location (may contain cells and/or lines). Cell and Line are different production processes, each needing different SA applets. Cell operators and line operators are actors in that SA. The cell/line itself is not an SA. Follows the same SA governance model as POS (ADR 0005). SA context for each MO is recorded in association tables (
ov.sa_mo,ov.actor_sa_mo), not inmrp.productionitself (per ADR 0003).Cell vs. Line: Cell = batch/unit production (one MO = one vehicle); Line = continuous flow production. Each needs its own SA applet (
Assembly-Cellnow;Assembly-Linefuture).
SA Structure for Assembly-Cell¶
| SA Level | Analog | Role |
|---|---|---|
| Production Location SA | Retail shop SA | May contain cells and/or lines; cell/line operators are actors within this SA |
| Line Worker (actor) | Cashier (agent) | Executes assembly on assigned MOs |
| Line Manager (SA manager) | SA Manager | Manages line, aggregates reports |
SA Governance Rules for Assembly-Cell¶
| Rule | Description |
|---|---|
| SA governs MO via association table | Every MO has a corresponding ov.sa_mo row linking it to its governing SA + assigned worker |
| Worker accesses assigned MOs | Worker (actor) sees only MOs linked to them in ov.actor_sa_mo (or unassigned within SA) |
| Manager aggregates | Line manager (SA manager) sees all MOs within the line |
| Parent SA rolls up | Parent SA (regional) sees roll-up over child SAs (multiple lines) |
| Fleet/ITEM scoped to SA | Fleets/ITEMs produced by this line are governed by this SA (until transfer) |
Association Tables for Assembly-Cell¶
| Table | Purpose | Points To |
|---|---|---|
ov.sa_fleet |
Which SA governs this fleet? | ov.serviced_account + ov.fleet |
ov.actor_sa_fleet |
Which actor (worker) is assigned to this fleet? | ov.sa_fleet + res.partner |
ov.sa_mo |
Which SA governs this MO? | ov.serviced_account + mrp.production |
ov.actor_sa_mo |
Which worker is assigned to this MO? | ov.sa_mo + res.partner |
SA Context Recorded via Association Tables¶
When an MO is created, a corresponding row is inserted into ov.sa_mo linking the MO to its governing SA. When a worker is assigned, a row is inserted into ov.actor_sa_mo. No fields on mrp.production are modified.
SA context row in ov.sa_mo¶
Each row in ov.sa_mo links one MO to its governing SA:
# ov.sa_mo row (association table — does NOT modify mrp.production)
{
'sa_id': ov.serviced_account.id, # Which SA (Production Location) governs this MO
'mo_id': mrp.production.id, # Which MO
'created_at': datetime, # When the MO was created
}
Worker assignment is a separate row in ov.actor_sa_mo:
# ov.actor_sa_mo row (association table)
{
'sa_mo_id': ov.sa_mo.id, # Link to the SA-MO association
'actor_id': res.partner.id, # Which worker is assigned to this MO
'assigned_at': datetime, # When the worker was assigned
}
5. Assembly-Cell Worker Identity — PA Applet Login + SA Context (SA = Production Location)¶
Design Principle¶
All assembly workers are identified at both PA applet level and SA context level. The Assembly-Cell applet is a PA applet (not Odoo PWA). Worker identity is managed by PA, not Odoo native login. Context of each action is recorded via
ov.actor_sa_morows (per ADR 0003: no modification to native Odoo models).
Two-Level Identity (Same as POS)¶
| Level | What It Means | PA/SA Mechanism |
|---|---|---|
| PA applet level | Worker has a PA applet identity (login to Assembly-Cell applet) | PA session, res.partner record |
| SA context level | Worker is a member of specific assembly SAs with defined role | ov.membership record, ov.actor_sa_* records |
Worker Action Context: recorded in ov.actor_sa_mo¶
Every assembly action (CKD manifest scan, component capture, MO complete) triggers a row in ov.actor_sa_mo. The SA + actor are never written to mrp.production.
Example ov.actor_sa_mo row for worker assignment:
# ov.actor_sa_mo row (association table — no modification to mrp.production)
{
'sa_mo_id': ov.sa_mo.id, # Link to the SA-MO association
'actor_id': res.partner.id, # Which worker
'assigned_at': datetime, # When assigned
}
For action-level tracking (optional future extension), ov.sa_mo_event rows capture:
{
'sa_mo_id': ov.sa_mo.id,
'actor_id': res.partner.id,
'action': 'scan_manifest' | 'capture_component' | 'complete',
'timestamp': datetime,
}
6. Reporting Privileges — Three Tiers (Same as POS)¶
Design Principle¶
Each worker can view history data within an SA context. Aggregation among members of an SA is the privilege of the SA Manager only. Aggregation along the SA tree is the sole privilege of the parent SA over its child-SAs.
Tier 1: Worker — View Own + Assigned¶
A worker (Production Location actor) can view:
- Their own MO history within the SA
- MOs explicitly assigned to them (if
scope_policy='assigned_only') - Unassigned MOs within the SA (if
scope_policy='assigned_plus_unassigned')
They cannot see other workers' MOs within the same SA (unless scope_policy='sa_wide').
Tier 2: SA Manager — Aggregate Within SA¶
The SA Manager (ov.membership.is_sa_manager=True) can:
- View all MOs within their SA (regardless of which worker executed them)
- Generate aggregate reports (total units produced, yield rate, defect rate)
- Export data for analysis
- Manage membership (add/remove workers, change roles)
They cannot see MOs in other SAs (unless those SAs are child-SAs).
Tier 3: Parent SA — Aggregate Over Child-SAs¶
A parent SA (assembly regional SA with multiple lines) can:
- View MOs across all child-SAs in the SA tree
- Generate roll-up reports ("total production across all lines")
- Compare performance across child-SAs
7. ITEM Production Tracking — Build Record Data¶
Design Principle¶
The Build Record (
ov.asset.registry) captures what was produced (component serials, worker, line, timestamps). The SA governance layer captures who, where, and how efficiently.
Production Data Captured at ITEM Level¶
| Data | Source | Purpose |
|---|---|---|
mo_id |
mrp.production |
Which MO produced this ITEM |
sa_id |
ov.sa_mo |
Which Production Location produced it |
actor_id |
ov.actor_sa_mo |
Which worker produced it |
oem_id |
ov.asset.registry |
Business identifier for the ITEM |
build_record_status |
ov.asset.registry.status |
Draft / Finalized |
chassis_serial |
ov.asset.component |
Chassis serial (captured at box-open) |
mcu_serial |
ov.asset.component |
MCU serial (captured at box-open) |
motor_serial |
ov.asset.component |
Motor serial (captured at box-open) |
vcu_serial |
ov.asset.component |
VCU serial (captured at box-open) |
yield_rate |
MO time tracking | How efficiently |
quality_result |
Quality check | Pass/Fail/Rework |
This data enables:
- Productivity tracking: Which worker/line is most efficient?
- Quality tracking: Which worker/line has highest defect rate?
- Traceability: If a defect is found, which worker/line produced the affected ITEMs?
- Component traceability: Which OEM made the chassis/MCU/motor/VCU? (future: when components are serialized in Odoo)
8. SA Scope = ACL (Same as POS)¶
Design Principle¶
SA scope is a field decision, but carries heavy responsibility — because this defines ACL.
What "Field Decision" Means for Assembly¶
The operations/field team decides:
- How many Production Location SAs to create (one per cell? per line? per shift? per product model?)
- Which workers belong to which line SA
- What
scope_policyeach membership has
These are business decisions, not technical decisions. But they have security consequences — because SA scope = access scope.
SA Scope IS ACL¶
There is no separate "ACL configuration" step. The act of:
- Creating an SA (
ov.serviced_account) for a Production Location - Adding a worker to it (
ov.membership) - Setting
scope_policyon the membership
... is the ACL configuration. The ir.rule (Track B in ADR 0006) then enforces this ACL on every query.
9. Open Questions¶
9.1 Multi-Line Worker ✅ (Answered)¶
Can a worker belong to multiple Production Location SAs? Yes — like a cashier with multiple shop memberships.
9.2 Cross-Line Material Transfer ✅ (Answered)¶
Can CKD materials be transferred between Production Location SAs? Yes — but must be governed by a parent SA or material management SA.
9.3 Quality Check SA¶
Is quality check a separate SA, or part of the Production Location SA? (Open — may be separate for independence.)
9.4 ITEM Transfer Between SAs¶
When an ITEM (FG serial) is transferred from assembly SA to sales SA, how is SA context updated? (Likely: new ov.sa_fleet record for the sales SA, old one marked inactive.)
9.5 Component Serialization (Future Deliberation)¶
Should we create Odoo Serials for components (chassis, MCU, motor, VCU) at CKD box-open, to enable direct traceability to component OEMs (not just via the CKD box)? Currently: components are lot-tracked in Odoo; serials are captured as text in ov.asset.component. Creating Odoo Serials at box-open would enable direct OEM traceability, but adds complexity. Defer to future.
Consequences¶
- Assembly-Cell applet — PA applet (physical desktop + tablet), NOT Odoo PWA; form-based workflow
- CKD box = one MO = one vehicle — no batch MOs for assembly; one-to-one relationship
- Worker identity is two-level — PA applet login + SA context (membership), action context recorded via
ov.actor_sa_morows - ITEM-level tracking —
ov.fleet,ov.fleet_item,stock.production.lotare the basis; SA governance attaches viaov.sa_fleet - MO context via association tables — every MO has a
ov.sa_morow linking SA + actor; association tables enforce subset rule - Reporting is three-tier — worker sees own, SA manager sees all in SA, parent SA sees roll-up over children
- SA scope = ACL — field team decides scope, but this directly defines access control; heavy responsibility
- Build Record lifecycle — draft created at CKD manifest scan, progressively updated as components are scanned, finalized at manager sign-off (when Serial + OEM-ID are created)
- Component serials — lot-tracked in Odoo, captured as text in
ov.asset.component; future: consider creating Odoo Serials at box-open for direct OEM traceability (deferred)
Cross-Reference¶
- Assembly-Cell applet (PA): this ADR (defines the Odoo model extensions, PA applet workflow, SA governance)
- Build Record (domain):
asset-lifecycle/docs/9-warranty/2-build-record-and-asset-passport.md(cross-reference to be added when that doc is created) - Asset registry implementation:
dirac-odoo/docs/adr/0007-asset-registry-and-component-model-implementation.md - SA governance (POS analog):
dirac-odoo/docs/adr/0005-v3-pos-applet-design.md - Fleet/ITEM model:
dirac-odoo/docs/adr/0004-fleet-and-item-governance-layer.md - Association model pattern:
dirac-odoo/docs/adr/0003-v3-association-model-migration-pattern.md