# LabaBersih v2 — Blueprint Future (Phase 2 & 3)

> Konten ini di-split dari `v2-execution-blueprint.md` untuk mengurangi ukuran file.
> Referensi: ini adalah detail Phase 2/3 yang BELUM dikerjakan sekarang.
> File utama: `rules/v2-execution-blueprint.md` — semua Phase 1 content tetap di sana.

---

## 1. Domain 4 Phase 2 — Full Fulfillment Flow

> Extracted dari Domain 4, section 4b.
> Context: `Lababersih.Fulfillment` (BARU, Phase 2)

**BELUM implement. Design lengkap di sini untuk reference.**

**Flow:**
```
Order masuk → reserve stock
  ↓
FulfillmentOrder (batch orders → 1 dokumen)
  → generate PickList (group by Area + SKU)
  ↓
Kepala Gudang approve (TTD digital)
  ↓
Picker ambil barang per Area → confirm qty_picked
  ↓
Packer scan order → verify items → tempel resi → pack
  → SETIAP RESI TRACK SIAPA YANG PACK (PIC packer)
  ↓
Sort by ekspedisi (JNE pile, J&T pile — via courier_mapping)
  ↓
Courier Handover (manifest TTD staff + driver)
  ↓
Side effects (event-driven):
  → stock deduction (FEFO lot consumption)
  → journal generation
  → audit trail
```

**Schema Phase 2 (12 tabel baru — implement NANTI):**

```sql
-- Fulfillment Order
CREATE TABLE fulfillment_orders (
  id              VARCHAR PRIMARY KEY,  -- "FO2603-00012"
  org_id          UUID NOT NULL REFERENCES organizations(id),
  warehouse_id    UUID NOT NULL REFERENCES warehouses(id),
  status          VARCHAR NOT NULL DEFAULT 'draft',
    -- draft → pick_requested → approved → picking → picked → packing → shipped → completed
  requested_by    UUID REFERENCES users(id),
  approved_by     UUID REFERENCES users(id),
  approved_at     UTC_DATETIME,
  notes           TEXT,
  timestamps
);

CREATE TABLE fulfillment_order_items (
  id                    BINARY_ID PRIMARY KEY,
  fulfillment_order_id  VARCHAR NOT NULL REFERENCES fulfillment_orders(id),
  order_id              VARCHAR NOT NULL REFERENCES orders(id),
  order_item_id         BINARY_ID NOT NULL REFERENCES order_items(id),
  qty_requested         INTEGER NOT NULL,
  qty_fulfilled         INTEGER DEFAULT 0,
  status                VARCHAR DEFAULT 'pending',  -- pending/fulfilled/partial/cancelled
  timestamps
);

-- Pick List
CREATE TABLE pick_lists (
  id                    VARCHAR PRIMARY KEY,  -- "PL2603-00012"
  fulfillment_order_id  VARCHAR NOT NULL REFERENCES fulfillment_orders(id),
  warehouse_id          UUID NOT NULL REFERENCES warehouses(id),
  assigned_to           UUID REFERENCES users(id),  -- picker
  approved_by           UUID REFERENCES users(id),
  approved_at           UTC_DATETIME,
  status                VARCHAR DEFAULT 'pending',  -- pending→approved→picking→completed
  timestamps
);

CREATE TABLE pick_list_items (
  id              BINARY_ID PRIMARY KEY,
  pick_list_id    VARCHAR NOT NULL REFERENCES pick_lists(id),
  product_id      UUID NOT NULL REFERENCES products(id),
  sku             VARCHAR,
  warehouse_area  VARCHAR,  -- "Area 1"
  qty_needed      INTEGER NOT NULL,
  qty_picked      INTEGER DEFAULT 0,
  picked_at       UTC_DATETIME,
  timestamps
);

-- Shipment (enhanced)
CREATE TABLE shipments (
  id                    VARCHAR PRIMARY KEY,  -- "SHP-260331-00412"
  order_id              VARCHAR NOT NULL REFERENCES orders(id),
  fulfillment_order_id  VARCHAR REFERENCES fulfillment_orders(id),
  no_resi               VARCHAR,
  courier_raw           VARCHAR,  -- "Shopee Cargo JNE" as-is
  courier               VARCHAR,  -- "JNE" resolved
  packed_by             UUID REFERENCES users(id),  -- PIC PACKER
  packed_at             UTC_DATETIME,
  verified_items        BOOLEAN DEFAULT false,
  handover_id           VARCHAR REFERENCES courier_handovers(id),
  status                VARCHAR DEFAULT 'created',
    -- created→packed→handed_over→in_transit→delivered
  shipped_at            UTC_DATETIME,
  delivered_at          UTC_DATETIME,
  timestamps
);

-- Courier Handover
CREATE TABLE courier_handovers (
  id                VARCHAR PRIMARY KEY,  -- "HO-260331-JNT-001"
  org_id            UUID NOT NULL REFERENCES organizations(id),
  warehouse_id      UUID NOT NULL REFERENCES warehouses(id),
  courier           VARCHAR NOT NULL,
  total_packages    INTEGER,
  handed_by         UUID REFERENCES users(id),
  received_by_name  VARCHAR,  -- nama driver
  handed_at         UTC_DATETIME,
  status            VARCHAR DEFAULT 'prepared',  -- prepared→handed_over
  notes             TEXT,
  timestamps
);

-- Packing Incident (QC)
CREATE TABLE packing_incidents (
  id              BINARY_ID PRIMARY KEY,
  shipment_id     VARCHAR REFERENCES shipments(id),
  packer_id       UUID REFERENCES users(id),
  type            VARCHAR NOT NULL,
    -- wrong_item/wrong_qty/damaged_pack/wrong_label/missing_item
  penalty_points  INTEGER DEFAULT 0,
  reported_by     UUID REFERENCES users(id),
  evidence        TEXT,
  timestamps
);

-- GRN (Goods Receipt Note) — enhance PO receive
CREATE TABLE goods_receipt_notes (
  id                    VARCHAR PRIMARY KEY,  -- "GRN-260331-001"
  org_id                UUID NOT NULL REFERENCES organizations(id),
  warehouse_id          UUID NOT NULL REFERENCES warehouses(id),
  po_id                 VARCHAR NOT NULL REFERENCES purchase_orders(id),
  supplier_id           UUID REFERENCES suppliers(id),
  delivery_type         VARCHAR,  -- factory_driver/third_party_courier
  driver_name           VARCHAR,
  received_by           UUID REFERENCES users(id),
  inspected_by          UUID REFERENCES users(id),
  requires_return_copy  BOOLEAN DEFAULT false,
  status                VARCHAR DEFAULT 'draft',  -- draft→confirmed→completed
  notes                 TEXT,
  timestamps
);

CREATE TABLE grn_items (
  id                BINARY_ID PRIMARY KEY,
  grn_id            VARCHAR NOT NULL REFERENCES goods_receipt_notes(id),
  product_id        UUID NOT NULL REFERENCES products(id),
  qty_ordered       INTEGER NOT NULL,
  qty_received      INTEGER NOT NULL,
  qty_rejected      INTEGER DEFAULT 0,
  rejection_reason  VARCHAR,
  expiry_date       DATE,  -- untuk FEFO lot
  timestamps
);
```

**Context Phase 2 — `Lababersih.Fulfillment` (BARU):**
```elixir
# FulfillmentOrder
create_fulfillment_order(org_id, warehouse_id, order_ids)
approve_fulfillment_order(fo_id, approved_by_user_id)

# PickList
confirm_pick(pick_list_id, picked_items)

# Packing
pack_shipment(%{order_id, fo_id, packer_id, no_resi, courier_raw})

# Ship (REPLACE ship_order)
ship_shipment(shipment_id, actor_email)
  # Atomic: status + FEFO + jurnal + audit (sama dengan ship_order, + packed_by tracking)

# Courier
resolve_courier(org_id, raw_name)
create_handover(org_id, warehouse_id, courier, shipment_ids)
confirm_handover(handover_id, received_by_name)

# Incident
report_incident(shipment_id, attrs)

# GRN (enhance PO receive)
create_grn(po_id, attrs)  # replace receive_po
confirm_grn(grn_id)
```

**Execution Steps — Domain 4 Phase 2:**
```
Step F5: Migration — 8 new tables (fulfillment_orders, fo_items, pick_lists, pl_items, shipments, courier_handovers, packing_incidents, grn, grn_items)
Step F6: Schema — 9 schema files
Step F7: Context — Lababersih.Fulfillment (full)
Step F8: Context — Update Purchasing (GRN replace receive_po)
Step F9: Test — Full fulfillment flow
Step F10: UI — FulfillmentOrder + PickList + Shipment + Handover pages
```

---

## 2. Domain 6 — Intelligence (Phase 3)

> Definisi: Data analytics & prediction untuk keputusan bisnis.
> Context: `Lababersih.Intelligence` (BARU, Phase 3)
> Dependency: Orders + Inventory + Accounting HARUS mature dulu

### 6a. Demand Snapshots

**Schema:**
```sql
CREATE TABLE demand_snapshots (
  id              BINARY_ID PRIMARY KEY,
  org_id          UUID NOT NULL,
  product_id      UUID NOT NULL REFERENCES products(id),
  warehouse_id    UUID NOT NULL REFERENCES warehouses(id),
  date            DATE NOT NULL,
  qty_ordered     INTEGER DEFAULT 0,
  qty_shipped     INTEGER DEFAULT 0,
  source_breakdown JSONB,  -- {tiktok: N, shopee: N, mengantar: N}
  is_spike        BOOLEAN DEFAULT false,
  timestamps
);
CREATE UNIQUE INDEX demand_snapshots_unique ON demand_snapshots(org_id, product_id, warehouse_id, date);
```

### 6b. ABC-XYZ Classification

```elixir
classify_products(org_id)
  # A = top 80% revenue, B = next 15%, C = bottom 5%
  # X = CV < 0.5 (stable), Y = 0.5-1.0 (variable), Z = > 1.0 (unpredictable)
  # AX = high revenue, stable demand → auto-reorder
  # CZ = low revenue, unpredictable → minimal stock
```

### 6c. Spike Detection

```elixir
detect_spikes(org_id, date)
  # Per product: compare today vs 14-day moving average
  # IF > 2× MA → create spike_event
  # Source: planned_campaign / organic_surge / unknown
```

### 6d. Campaign Planning

**Schema:**
```sql
CREATE TABLE campaign_plans (
  id              BINARY_ID PRIMARY KEY,
  org_id          UUID NOT NULL,
  product_id      UUID NOT NULL REFERENCES products(id),
  campaign_type   VARCHAR,  -- tiktok_live/flash_sale/ads_push/price_cut/bundle
  campaign_date   DATE,
  duration_days   INTEGER,
  campaign_price  DECIMAL,
  normal_price    DECIMAL,
  estimated_volume INTEGER,
  actual_volume    INTEGER,
  po_id           VARCHAR REFERENCES purchase_orders(id),
  status          VARCHAR DEFAULT 'planning',
    -- planning→stock_ready→active→completed
  accuracy_pct    DECIMAL,
  timestamps
);
```

### 6e. Reorder Suggestion

```elixir
reorder_suggestions(org_id)
  # Per product (A/B class):
  #   daily_demand = 14-day moving average
  #   reorder_point = daily_demand × lead_time + safety_stock
  #   days_remaining = available_stock / daily_demand
  #   IF stock < reorder_point → suggest PO
```

### 6f. New Product Tracking

```sql
CREATE TABLE new_product_tracking (
  id                      BINARY_ID PRIMARY KEY,
  product_id              UUID NOT NULL REFERENCES products(id),
  first_po_date           DATE,
  initial_stock           INTEGER,
  benchmark_sell_through  DECIMAL,
  actual_sell_through     DECIMAL,
  signal                  VARCHAR,  -- hot/normal/slow
  signal_detected_at      UTC_DATETIME,
  timestamps
);
```

### EXECUTION STEPS — Domain 6 (Phase 3)

```
Step I1: Migration — demand_snapshots, campaign_plans, spike_events, new_product_tracking
Step I2: Schema — 4 schema files
Step I3: Context — Lababersih.Intelligence
Step I4: Oban Worker — DailyDemandSnapshotWorker
Step I5: Oban Worker — MonthlyABCXYZWorker
Step I6: Context — spike detection, reorder suggestions
Step I7: Context — campaign planning (create, complete, accuracy)
Step I8: UI — Intelligence dashboard
Step I9: UI — Reorder suggestion → auto-create PO draft
```

---

## 3. Cross-Domain ERD

```
┌─────────────────────────────────────────────────────────────────┐
│                        ORGANIZATION                             │
│  (1 akun LabaBersih = 1 org)                                   │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐      │
│  │ Legal Entity  │    │  Warehouse   │    │   Account    │      │
│  │ (PT/CV/pers)  │    │ (gudang)     │    │ (COA)        │      │
│  └──────┬───────┘    └──────┬───────┘    └──────┬───────┘      │
│         │                   │                    │               │
│    owns │              has  │              auto- │               │
│         │                   │            generate│               │
│  ┌──────▼───────┐    ┌──────▼───────┐           │               │
│  │    Store      │◄───│  Warehouse   │           │               │
│  │ (toko mktpl)  │def │  Location    │           │               │
│  │ 8 account FKs─┼────┼──────────────┼───────────┘               │
│  └──────┬───────┘    └──────────────┘                           │
│         │                                                       │
│    has  │                                                       │
│         │                                                       │
│  ┌──────▼───────┐    ┌──────────────┐    ┌──────────────┐      │
│  │    Order      │    │   Product    │    │   Supplier   │      │
│  │ (pesanan)     │    │ (simple/bndl)│    │              │      │
│  └──────┬───────┘    └──────┬───────┘    └──────┬───────┘      │
│         │                   │                    │               │
│    has  │              has  │               has  │               │
│         │                   │                    │               │
│  ┌──────▼───────┐    ┌──────▼───────┐    ┌──────▼───────┐      │
│  │  Order Item   │    │ Bundle Item  │    │ Purchase     │      │
│  │ (line item)   │    │ (BOM)        │    │ Order        │      │
│  └──────────────┘    ├──────────────┤    └──────┬───────┘      │
│                      │ Unit Conv.   │           │               │
│  ┌──────────────┐    ├──────────────┤    ┌──────▼───────┐      │
│  │ Return Record │    │ Inv. Lot     │◄───│ (receive/GRN)│      │
│  │ (RTS)         │    │ (FEFO)       │    └──────────────┘      │
│  └──────────────┘    ├──────────────┤                           │
│                      │ Lot Consumpt.│                           │
│  ┌──────────────┐    ├──────────────┤                           │
│  │ Reconcil.    │    │ Stock Txn    │                           │
│  │ (settlement)  │    └──────────────┘                           │
│  └──────────────┘                                               │
│                                                                 │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐      │
│  │ Journal Entry │    │ Tax Record   │    │ Courier Map  │      │
│  │ + Lines       │    │ (PPh/PPN)    │    │ (raw→induk)  │      │
│  └──────────────┘    └──────────────┘    └──────────────┘      │
│                                                                 │
│  ── Phase 2 ──────────────────────────────────────────────      │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐      │
│  │ Fulfillment  │    │  Pick List   │    │  Shipment    │      │
│  │ Order        │    │              │    │ (packed_by)   │      │
│  └──────────────┘    └──────────────┘    └──────────────┘      │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐      │
│  │ Courier      │    │  GRN         │    │ Packing      │      │
│  │ Handover     │    │ (GRN Items)  │    │ Incident     │      │
│  └──────────────┘    └──────────────┘    └──────────────┘      │
│                                                                 │
│  ── Phase 3 ──────────────────────────────────────────────      │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐      │
│  │ Demand       │    │  Campaign    │    │ Spike Event  │      │
│  │ Snapshot     │    │  Plan        │    │              │      │
│  └──────────────┘    └──────────────┘    └──────────────┘      │
│  ┌──────────────┐    ┌──────────────┐                           │
│  │ New Product  │    │ Recurring    │                           │
│  │ Tracking     │    │ Expense      │                           │
│  └──────────────┘    └──────────────┘                           │
└─────────────────────────────────────────────────────────────────┘
```

### Key Relationships

```
Organization 1──N Legal Entity 1──N Store N──1 Warehouse
Organization 1──N Warehouse 1──N Warehouse Location (self-ref hierarchy)
Organization 1──N Product 1──N Bundle Item (BOM)
Organization 1──N Product 1──N Unit Conversion
Organization 1──N Product 1──N Inventory Lot (per warehouse, FEFO)
Organization 1──N Courier Mapping

Store N──1 Legal Entity
Store 1──8 Account (FK, auto-generated)
Store N──1 Warehouse (default)

Order N──1 Store
Order 1──N Order Item N──1 Product
Order.customer_name + customer_phone (denormalized, no customer table)

Legal Entity 1──N Tax Record (per bulan)
Legal Entity 1──N Purchase Order
Legal Entity 1──N Inventory Lot

Journal Entry 1──N Journal Entry Line
Journal Entry Line.order_id → Order (per-order tracking, nullable)
Journal Entry.batch_date + batch_type (daily summary)
```
