Skip to content

ADR 0008: Assembly-Cell Process with SA Governance

Status

Proposed

Date

2026-06-18


Context

dirac-odoo already defines:

  1. SA governance model (ADR 0005): SAs govern POS transactions, with agents (cashiers), SA managers, and three-tier reporting.
  2. Fleet/ITEM model (ADR 0004): ov.fleet, ov.fleet_item, and native Odoo stock.production.lot (serialized ITEM).
  3. Asset registry (ADR 0007): ov.asset.registry and ov.asset.component created at MO completion.
  4. 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 of ov.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 MaterialsOVT/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/AssemblyOVT/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 of ov.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 Odoo stock.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:

  1. Assigned to a fleet (ov.fleet_item)
  2. Assigned to an SA (ov.sa_fleet)
  3. Transferred to sales (stock move to retail location)
  4. 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 in mrp.production itself (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-Cell now; Assembly-Line future).

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_mo rows (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_policy each 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:

  1. Creating an SA (ov.serviced_account) for a Production Location
  2. Adding a worker to it (ov.membership)
  3. Setting scope_policy on 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

  1. Assembly-Cell applet — PA applet (physical desktop + tablet), NOT Odoo PWA; form-based workflow
  2. CKD box = one MO = one vehicle — no batch MOs for assembly; one-to-one relationship
  3. Worker identity is two-level — PA applet login + SA context (membership), action context recorded via ov.actor_sa_mo rows
  4. ITEM-level trackingov.fleet, ov.fleet_item, stock.production.lot are the basis; SA governance attaches via ov.sa_fleet
  5. MO context via association tables — every MO has a ov.sa_mo row linking SA + actor; association tables enforce subset rule
  6. Reporting is three-tier — worker sees own, SA manager sees all in SA, parent SA sees roll-up over children
  7. SA scope = ACL — field team decides scope, but this directly defines access control; heavy responsibility
  8. 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)
  9. 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