# Scan Station Module — Task Plan
> Module: `scanstation` | Created: 2026-03-25

---

## Background & Problem Statement

The manufacturing process is dirty — parts cannot carry labels through most operations.
The **only viable point for affixing a physical barcode label** to a part is **after the Wash operation**.

However, the **job traveler (job wallet)** is a paper document that accompanies the job from the start.
It can carry a barcode/QR from the moment the MO is printed — making it the primary scan target
for tracking progress through the production flow.

### Goals
1. **Flow B (Priority 1)** — Scan the job wallet traveler at each workstation → auto-update `llx_mrp_mo_optracking`
2. **Flow A (Priority 2)** — Print a part label at the Wash station → physical barcode sticker on part
3. **Phase 3** — Integrate scan events into scrap, OEE cycle time, doccontrol WI display

> **Independence rule:** scanstation has NO dependency on any custom module.
> It reads/writes only standard Dolibarr core tables and its own `llx_scanstation_log`.
> QR codes on job wallets may be generated by any tool — scanstation only parses the format.

---

## Module Architecture

```
custom/scanstation/
├── .gitattributes
├── scanstation_task.md         ← this file
├── core/
│   └── modules/
│       └── modScanStation.class.php
├── sql/
│   ├── install.sql
│   └── uninstall.sql
├── admin/
│   └── setup.php
├── css/
│   └── scanstation.css         (kiosk-optimized styles)
├── js/
│   └── scanstation.js          (barcode input handler)
├── ajax/
│   └── scan_action.php         (POST endpoint → optracking write)
├── scan_station.php            (kiosk UI — tablet-friendly)
├── label_print.php             (part label trigger — post-Wash)
└── label_templates/
    ├── part_label.zpl          (Zebra ZPL template)
    └── part_label.pdf.php      (browser PDF fallback)
```

---

## Database

### New table: `llx_scanstation_log`

```sql
CREATE TABLE llx_scanstation_log (
    rowid          INT AUTO_INCREMENT PRIMARY KEY,
    fk_mo          INT NOT NULL,               -- llx_mrp_mo.rowid
    job_number     VARCHAR(64),                -- from llx_mrp_mo_extrafields
    fk_ws          INT,                        -- llx_workstation.rowid
    op_rank        INT,                        -- operation sequence number
    scan_type      VARCHAR(20) NOT NULL,       -- 'traveler' | 'part_label' | 'wi'
    scanned_data   VARCHAR(512),               -- raw barcode string
    qty_confirmed  DECIMAL(10,2) DEFAULT 1,
    fk_user        INT,
    date_scan      DATETIME NOT NULL,
    result         VARCHAR(20) DEFAULT 'ok',   -- 'ok' | 'error' | 'mismatch'
    note           VARCHAR(255),
    entity         INT NOT NULL DEFAULT 1
) ENGINE=InnoDB;
```

No new tables required for optracking — writes go directly into existing `llx_mrp_mo_optracking`.

---

## Barcode Encoding Standard

### Traveler / Job Wallet QR code
Encoded in `paper_work.php` print header:
```
MO:{mo_rowid}|JOB:{job_number}|ENTITY:{entity}
```
Example: `MO:187|JOB:JB-0042|ENTITY:1`

Format: **QR Code** (printable, readable after handling)
Library: PHP `endroid/qr-code` or JS `qrcodejs` rendered client-side

### Part Label barcode (post-Wash)
```
PART:{ref}|MO:{mo_rowid}|JOB:{job_number}|SER:{serial_or_lot}
```
Format: **Code128** horizontal bar + QR below
Serial: auto-incremented per MO if no lot tracking, else use Dolibarr lot number

---

## PHASE 1 — Job Wallet Scan + Kiosk UI

### 1.1 QR format (read-only, scanstation does not generate this)
Scanstation expects to receive QR codes in the format:
```
MO:{mo_rowid}|JOB:{job_number}|ENTITY:{entity}
```
This format may be printed by any tool (job card, traveler, etc.) — scanstation has no opinion on the source.

### 1.2 scan_station.php — Kiosk UI

**Screen layout (tablet-optimized, 1024×768):**

```
┌─────────────────────────────────────────────────┐
│  SCAN STATION  |  [Workstation: CNC-01 ▼]  [👤] │
├─────────────────────────────────────────────────┤
│                                                 │
│  [ 🔲 Scan traveler barcode or type MO# ]       │
│           ___________________________           │
│                                                 │
│  ┌───────────────────────────────────────────┐ │
│  │  MO:  MO-2026-0187   Part: 12345-A        │ │
│  │  Job: JB-0042        Customer: ACME       │ │
│  │  Next Op: ► DEBURR (op 30)                │ │
│  │  Qty to confirm: [ 1  ] [+] [-]           │ │
│  │                      [ ✅ CONFIRM ]        │ │
│  └───────────────────────────────────────────┘ │
│                                                 │
│  Last 5 scans:                                  │
│  ✅ MO-187 | GRIND  | 14:22 | op.30 | qty:1    │
│  ✅ MO-183 | DEBURR | 14:18 | op.20 | qty:2    │
└─────────────────────────────────────────────────┘
```

**Features:**
- Workstation selector (saved in cookie/session per terminal)
- Auto-focus input on page load — scanner types barcode as keyboard input
- Scan immediately triggers AJAX lookup → shows MO info + **dropdown list of all operations for that MO**
- Operator selects operation from list, sets qty → AJAX POST to `ajax/scan_action.php`
- Shows last 5 scans as confirmation log
- Works without full Dolibarr navigation (minimal chrome)
- **Auth: standard Dolibarr login** — operator logs in with their own Dolibarr account; `$user` is available via `main.inc.php` as normal

### 1.3 ajax/scan_action.php

**Accepts POST:**
```json
{
  "action":     "confirm_op",
  "mo_rowid":   187,
  "job_number": "JB-0042",
  "fk_ws":      3,
  "op_rank":    30,
  "qty":        1,
  "fk_user":    5
}
```

**Actions:**
1. Validate MO exists + op_rank is valid for that MO
2. INSERT into `llx_mrp_mo_optracking` (log-style — each scan = new row, `fk_user` = logged-in user)
3. INSERT into `llx_scanstation_log`
4. Return JSON: `{status, mo_ref, part_ref, message}`

**Returns (success):**
```json
{
  "status":   "ok",
  "mo_ref":   "MO-2026-0187",
  "part_ref": "12345-A",
  "op_done":  "DEBURR (op 30)",
  "next_op":  "GRIND (op 40)",
  "message":  "Operation confirmed"
}
```

---

## PHASE 2 — Part Labels (Post-Wash)

### 2.1 Trigger
- Button **"Print Part Label"** added to the scan confirmation screen in `scan_station.php`
- Only active when current op is the Wash operation (configurable op_rank or op name in setup)
- Links to: `/custom/scanstation/label_print.php?mo={rowid}&job={job_number}`

### 2.2 label_print.php
- Fetches MO + extrafields + lot/serial data
- Renders label preview in browser
- **Option A**: Sends ZPL directly to Zebra printer IP (configurable in setup.php)
- **Option B**: Opens PDF label in new tab for manual print

### 2.3 ZPL Template (label_templates/part_label.zpl)
```zpl
^XA
^FO20,20^BCN,60,Y,N,N^FD{CODE128_DATA}^FS
^FO20,100^A0N,20,20^FD{PART_REF}^FS
^FO20,125^A0N,18,18^FDMO: {MO_REF}^FS
^FO20,148^A0N,18,18^FDJob: {JOB_NUMBER}^FS
^FO20,171^A0N,16,16^FDDate: {DATE}^FS
^FO200,90^BQN,2,4^FDQA,{QR_DATA}^FS
^XZ
```

### 2.4 Cross-check scan (optional at later stations)
- Scan part label barcode at any post-wash station
- System reads `PART:...|MO:...|JOB:...`
- Cross-checks against traveler scan at same station
- Flags mismatch if job_numbers differ → logs to `scanstation_log` with `result='mismatch'`

---

## PHASE 3 — Integrations

### 3.1 Scrap
- Scan station shows "Report Scrap" button on the confirmation screen
- Operator enters scrap qty → INSERT into `llx_scanstation_log` with `scan_type='scrap'`, `qty_confirmed=N`
- **No dependency on scrap module** — scanstation logs scrap entirely in its own table
- Other modules (scrap, OEE) may read `llx_scanstation_log` independently

### 3.2 OEE — cycle time
- Cycle time per op = `date_scan(op_N+1) - date_scan(op_N)` per MO in `llx_scanstation_log`
- OEE module may read this table independently in the future

### 3.3 DocControl — WI display on scan
- If doccontrol exposes a URL by `part_ref` + `op_rank`, scan_station.php can link to it
- Display is iframe/link only — no code changes to doccontrol

### 3.4 NCR quick-raise
- "Raise NCR" button → redirect to NCR module URL with context params pre-filled
- No changes to NCR module

---

## Setup / Configuration (admin/setup.php)

| Setting | Type | Default | Description |
|---|---|---|---|
| `SCANSTATION_ZEBRA_IP` | string | — | Zebra printer IP for direct ZPL send |
| `SCANSTATION_ZEBRA_PORT` | int | 9100 | Zebra RAW port |
| `SCANSTATION_REQUIRE_PIN` | bool | 0 | ~~Require operator PIN~~ — removed, using standard Dolibarr login |
| `SCANSTATION_DEFAULT_QTY` | int | 1 | Default qty shown on scan confirmation |
| `SCANSTATION_SHOW_LAST_N` | int | 5 | Number of recent scans shown in kiosk |
| `SCANSTATION_LABEL_MODE` | select | `pdf` | `pdf` or `zebra` |
| `SCANSTATION_ENABLE_CROSSCHECK` | bool | 0 | Enable part label vs traveler cross-check |

---

## Module Integration Points

Scanstation is **fully standalone** — it modifies no other module files.
All integrations are one-directional: other modules may optionally read `llx_scanstation_log`.

| Direction | Notes |
|---|---|
| scanstation → `llx_mrp_mo_optracking` | Writes directly to core Dolibarr table |
| scanstation → `llx_scanstation_log` | Own log table |
| Future: scrap/oee/doccontrol | Those modules may read `llx_scanstation_log` independently — no changes by this module |

---

## ETAP Checklist

### PHASE 1 — Core Scan Station
- [ ] Create module directory + `modScanStation.class.php`
- [ ] `sql/install.sql` — `llx_scanstation_log`
- [ ] `scan_station.php` — kiosk UI (workstation selector + scan input)
- [ ] `ajax/scan_action.php` — validate + write optracking + log
- [x] QR format defined — `MO:{rowid}|JOB:{job_number}|ENTITY:{entity}` — parsed by scanstation, generated externally
- [ ] `admin/setup.php` — workstation config, pin setting
- [ ] `css/scanstation.css` — tablet-optimized layout
- [ ] `js/scanstation.js` — auto-focus, AJAX scan handler
- [ ] Git init + push to GitHub

### PHASE 2 — Part Labels
- [ ] `label_print.php` — label preview + print trigger
- [ ] `label_templates/part_label.zpl` — Zebra template
- [ ] `label_templates/part_label.pdf.php` — PDF fallback
- [ ] Add "Print Label" button to scan confirmation screen in `scan_station.php` (Wash op only)
- [ ] Test ZPL direct send to Zebra IP
- [ ] Cross-check logic in `scan_action.php`

### PHASE 3 — Integrations
- [ ] Scrap pre-fill from scan context
- [ ] OEE cycle time reader from `scanstation_log`
- [ ] DocControl WI fetch by part + op in kiosk
- [ ] NCR quick-raise from kiosk

---

## GitHub
- Repo: https://github.com/ciachoo/scanstation _(to be created)_
- Module number: `999104`
