# Production Order — Data Model

## Tables Read (external)

### `llx_mrp_mo`
Main MO record. Module creates rows here.

| Column | Type | Notes |
|---|---|---|
| rowid | INT | PK |
| ref | varchar | auto-generated by Dolibarr |
| label | varchar | `FG1007-04 Job 142 × 10` |
| fk_product | INT | FK → llx_product |
| qty | DECIMAL | total qty to produce |
| fk_warehouse | INT | target warehouse |
| status | INT | **1 = draft**, 2 = validated, 3 = in progress, 9 = produced |
| entity | INT | |
| datec | DATETIME | |

### `llx_mrp_mo_extrafields`
Extra fields for MO. Accessed via `job_number` column.

| Column | Notes |
|---|---|
| fk_object | FK → llx_mrp_mo.rowid |
| job_number | INT — sequential job number |
| working_station | varchar — primary WS override |

### `llx_mrp_production`
BOM lines on MO (materials consumed + operations produced).

| Column | Notes |
|---|---|
| fk_mo | FK → llx_mrp_mo |
| fk_product | FK → llx_product (material or service op) |
| fk_default_workstation | FK → llx_workstation_workstation |
| qty | quantity |
| rank | sort order (= op_no for ops, = seq_no for materials) |
| label | op label or material description |
| role | 'consumed' or 'produced' |
| position | sequential display position |

### `llx_mrp_mo_optracking`
Operation tracking per MO operation.

| Column | Notes |
|---|---|
| fk_mo | FK → llx_mrp_mo |
| op_rank | = op_no from routing |
| op_code | e.g. 'LASER', 'HEM1' |
| fk_workstation | FK → llx_workstation_workstation |
| qty_done | default 0 |
| status | 0=pending, 1=in progress, 2=done |

### `llx_product`
Products and services. Module reads:
- `ref`, `label`, `fk_product_type` (0=product, 1=service)
- `tobuy`, `tosell` flags
- SF/FG identified by ref prefix

### `llx_bom_bom` + `llx_bom_bomline`
Dolibarr BOM tables. `BomResolver` reads these first.

| Column | Notes |
|---|---|
| llx_bom_bom.fk_product | master product |
| llx_bom_bom.status | 1=active |
| llx_bom_bomline.fk_product | component |
| llx_bom_bomline.qty | qty per unit |
| llx_bom_bomline.rank | sequence |

### `llx_productionrules_routing`
Source of operations. Read-only from this module.

### `llx_productionrules_routing_confirm`
Checked before using routing: `confirmed=1` required.

### `llx_workstation_workstation`
| Column | Notes |
|---|---|
| rowid | PK |
| ref | e.g. 'LASER1', 'HEM2' |
| worksation_type | MACHINE / HUMAN / BOTH |
| nb_operators_required | default 1 |

---

## Tables Owned by This Module

### `llx_productionorder_draft`
Wizard session persistence. One draft per user (upsert on each preview).

```sql
CREATE TABLE IF NOT EXISTS llx_productionorder_draft (
  rowid       INT AUTO_INCREMENT PRIMARY KEY,
  entity      INT NOT NULL DEFAULT 1,
  fk_user     INT NOT NULL,
  fk_product  INT,
  qty         DECIMAL(10,3),
  job_number  INT,
  draft_data  LONGTEXT,
  datec       DATETIME,
  tms         TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY uk_draft_user_entity (fk_user, entity)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```

`draft_data` JSON schema:
```json
{
  "fk_product": 123,
  "qty": 10,
  "qty_raw": "61111x2",
  "job_number": 142,
  "label": "FG1007-04 Job 142 × 10",
  "routing_ops": [
    { "op_no": 10, "op_code": "LASER1", "label": "Laser profiling", "fk_workstation": 5, "ws_ref": "LASER1" }
  ],
  "materials": [
    { "seq_no": 10, "fk_product": 456, "ref": "M1950-1350-016-502", "label": "Sheet material", "qty": 2.0, "fi": true, "uom": "EA", "override": false }
  ]
}
```

---

## BOM Import Staging (future — step 9)

### `llx_productionorder_bom_import`
Holds Bill Of Materials.csv data after admin import.

```sql
CREATE TABLE IF NOT EXISTS llx_productionorder_bom_import (
  rowid             INT AUTO_INCREMENT PRIMARY KEY,
  entity            INT NOT NULL DEFAULT 1,
  master_ref        varchar(64) NOT NULL,
  master_desc       varchar(255),
  child_ref         varchar(64) NOT NULL,
  child_desc        varchar(255),
  seq_no            INT DEFAULT 0,
  qty               DECIMAL(12,6) NOT NULL DEFAULT 1,
  fi                TINYINT(1) DEFAULT 0,
  bom_notes         TEXT,
  child_uom         varchar(20),
  child_location    varchar(64),
  sundry            TINYINT(1) DEFAULT 0,
  child_group_code  varchar(64),
  datec             DATETIME,
  KEY idx_bom_master (master_ref, entity),
  KEY idx_bom_child  (child_ref)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```

---

## Data Flow Diagram

```
STOCKCODES.csv               Bill Of Materials.csv
(SF/FG Form Tool groups)     (281 FG × components)
        ↓                            ↓
  suggest_sf_parent.php     BomResolver::getMaterials()
        ↓                            ↓
  [STEP 1: Product + Qty]          [STEP 2: Preview]
        ↓                            ↓
  ProductionRoutingService          materials[]
  ::getRoutingByProduct()           (editable)
        ↓                            ↓
  routing_ops[]             ←————————┘
  (editable)
        ↓
  ProductionOrderService::createMO()
        ↓
  llx_mrp_mo (draft)
  llx_mrp_production (ops + materials)
  llx_mrp_mo_optracking
  llx_mrp_mo_extrafields (job_number)
        ↓
  → redirect to /mrp/mo_card.php?id=xxx
```

---

## Qty×Pack Parser

Input: `"61111×2"` or `"61111x2"` or plain `"10"`

```
if input matches /^(\d+(?:\.\d+)?)\s*[×x]\s*(\d+(?:\.\d+)?)$/i:
    qty   = float(group1) × float(group2)
    packs = int(group2)
else:
    qty   = float(input)
    packs = 1
```

Stored: `qty` on MO = total. `qty_raw` in draft_data for display.

---

## Key Relations

```
llx_product (FG)
  ↓ confirmed routing
llx_productionrules_routing_confirm
  ↓
llx_productionrules_routing (ops)
  ↓
llx_productionrules_routing_wc
  ↓
llx_workstation_workstation

llx_product (FG)
  ↓ BOM
llx_bom_bom
  ↓
llx_bom_bomline → llx_product (components)

→ combined into →

llx_mrp_mo
  ├── llx_mrp_mo_extrafields
  ├── llx_mrp_production (consumed: materials)
  ├── llx_mrp_production (produced: service ops)
  └── llx_mrp_mo_optracking
```
