> ## Documentation Index
> Fetch the complete documentation index at: https://docs.quinnsambal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Purchase order

<style>
  {`
    [class*="max-w-none"][class*="table"] {
      display: block !important;
      overflow-x: auto !important;
      max-width: 100% !important;
      width: 100% !important;
      flex-grow: 0 !important;
    }
    [class*="max-w-none"][class*="table"] > table {
      width: 100% !important;
      max-width: 100% !important;
      table-layout: fixed !important;
    }
    .mermaid {
      max-width: 100% !important;
      overflow-x: auto !important;
    }
    article svg[role="img"] {
      max-width: 100% !important;
      height: auto !important;
    }
    article img, .prose img {
      max-width: 100% !important;
      height: auto !important;
    }
    img[src*="LOGO"], img[src*="logo"] {
      max-width: 120px !important;
      max-height: 40px !important;
      width: auto !important;
      height: auto !important;
      object-fit: contain !important;
    }
    `}
</style>

***

title: "Purchase Order"
description: "Purchase order dengan approval workflow, partial receiving, supplier integration, dan tracking end-to-end."
-------------------------------------------------------------------------------------------------------------------------

# Purchase Order

<img src="https://mintcdn.com/quinnofspicy/e4f_upKhVWcjsUmM/docs/mintlify/screenshots/inventory/purchase-order.png?fit=max&auto=format&n=e4f_upKhVWcjsUmM&q=85&s=4944e0e5b4cec4293e629472a13c0f21" alt="Purchase Order" width="1920" height="1080" data-path="docs/mintlify/screenshots/inventory/purchase-order.png" />

Purchase Order (PO) adalah modul untuk memesan bahan baku dari supplier secara terstruktur. Setiap PO melewati workflow approval, bisa diterima secara parsial (sebagian), dan terintegrasi langsung dengan Supplier Management dan Stock Management. Sistem ini memastikan setiap pembelian tercatat, terkontrol, dan bisa dipertanggungjawabkan.

## Arsitektur Purchase Order

```mermaid theme={null}
graph TB
    subgraph "Pembuatan"
        NP[New PO<br/>Pilih Supplier & Produk]
        DR[Draft<br/>Simpan Sementara]
    end

    subgraph "Approval"
        AP[Approval Queue<br/>Menunggu Persetujuan]
        APR[Approved<br/>Disetujui]
        ARJ[Rejected<br/>Ditolak]
    end

    subgraph "Fulfillment"
        SN[Sent to Supplier<br/>PO Dikirim]
        PR[Partial Received<br/>Diterima Sebagian]
        FR[Full Received<br/>Diterima Lengkap]
    end

    subgraph "Pasca-terima"
        SI[Stock In<br/>Stok Bertambah]
        INV[Invoice<br/>Pembayaran]
        RT[Return<br/>Retur Barang]
    end

    NP --> DR --> AP
    AP --> APR --> SN
    AP --> ARJ --> NP
    SN --> PR --> FR
    PR --> FR
    FR --> SI --> INV
    FR --> RT
```

## Status PO — Lifecycle Lengkap

```mermaid theme={null}
stateDiagram-v2
    [*] --> Draft
    Draft --> PendingApproval: Submit
    PendingApproval --> Approved: Approve
    PendingApproval --> Rejected: Reject
    Rejected --> Draft: Revisi
    Approved --> Sent: Kirim ke Supplier
    Sent --> PartialReceived: Terima Sebagian
    PartialReceived --> FullReceived: Terima Sisa
    Sent --> FullReceived: Terima Lengkap
    FullReceived --> Closed: Selesai
    PartialReceived --> Closed: Close Manual
    Closed --> [*]
```

| Status | Ikon | Deskripsi | Aksi Selanjutnya |
| - | - | - | - |
| **Draft** | 📝 | Belum diajukan, masih bisa diedit | Submit untuk approval |
| **Pending Approval** | ⏳ | Menunggu persetujuan approver | Approve atau Reject |
| **Approved** | ✅ | Disetujui, siap dikirim ke supplier | Kirim ke supplier |
| **Sent** | 📤 | Sudah dikirim ke supplier, menunggu barang | Terima barang |
| **Partial Received** | 📦 | Barang diterima sebagian | Terima sisa atau close |
| **Full Received** | ✅ | Semua barang diterima lengkap | Stock in otomatis |
| **Rejected** | ❌ | Ditolak oleh approver | Revisi dan submit ulang |
| **Closed** | 🔒 | PO ditutup (manual atau otomatis) | Tidak ada aksi |

## Field PO — Detail Lengkap

### Header PO

| Field | Tipe | Wajib | Deskripsi |
| - | - | - | - |
| **PO Number** | Auto | Ya | Format: PO-{YYYYMMDD}-{seq} (contoh: PO-20261010-001) |
| **Supplier** | Dropdown | Ya | Pilih dari database supplier |
| **Tanggal PO** | Date | Ya | Tanggal pembuatan |
| **Expected Delivery** | Date | Ya | Perkiraan tanggal barang tiba |
| **Payment Terms** | Dropdown | Tidak | Cash, Net 7, Net 14, Net 30 |
| **Shipping Method** | Dropdown | Tidak | Pickup, Delivery, Courier |
| **Notes** | Text | Tidak | Catatan untuk supplier |
| **Total** | Auto-calc | - | Jumlah total semua item |

### Line Items

| Field | Tipe | Wajib | Deskripsi |
| - | - | - | - |
| **Produk** | Dropdown | Ya | Pilih dari katalog (bahan baku / produk) |
| **Quantity** | Number | Ya | Jumlah yang dipesan |
| **Unit** | Auto | - | Satuan (kg, liter, pcs, box) |
| **Harga per Unit** | Number | Ya | Harga satuan (auto-fill dari katalog supplier) |
| **Subtotal** | Auto-calc | - | Quantity x Harga |
| **Discount** | Number | Tidak | Diskon per item (%) |
| **Notes** | Text | Tidak | Catatan per item |

## Workflow Approval

```mermaid theme={null}
sequenceDiagram
    participant U as User (Inventory)
    participant S as Sistem
    participant A as Approver (Owner/Manager)
    participant SU as Supplier

    U->>S: Buat PO & Submit
    S->>S: Validasi (produk, qty, harga)
    S->>A: Notifikasi: PO Pending Approval
    Note over A: Review: qty, harga, budget
    
    alt Approve
        A->>S: Approve PO
        S->>S: Status → Approved
        S->>U: Notifikasi: PO Disetujui
        U->>S: Kirim PO ke Supplier
        S->>SU: PO terkirim (email/cetak)
    else Reject
        A->>S: Reject PO + alasan
        S->>U: Notifikasi: PO Ditolak + alasan
        U->>S: Revisi PO
    end
```

| Konfigurasi | Default | Bisa Diubah |
| - | - | - |
| Approver | Owner | Ya, bisa multi-level |
| Auto-approve threshold | PO \< Rp500.000 | Ya |
| Approval SLA | 24 jam | Ya |
| Escalation | Notifikasi reminder setiap 12 jam | Ya |

## Penerimaan Barang (Receiving)

### Partial Receiving

Sistem mendukung penerimaan parsial — kamu bisa menerima sebagian barang terlebih dahulu jika belum semua item tersedia:

```mermaid theme={null}
flowchart LR
    A[PO: 100 item] --> B[Terima 60 item]
    B --> C[Status: Partial Received<br/>Sisa: 40 item]
    C --> D[Terima 40 item lagi]
    D --> E[Status: Full Received<br/>Total: 100 item]
    E --> F[Stock In Otomatis]
```

### Proses Penerimaan

| Step | Aksi | Detail |
| - | - | - |
| 1 | Buka PO yang statusnya "Sent" | Lihat daftar item yang dipesan |
| 2 | Input quantity diterima per item | Bisa kurang dari qty yang dipesan |
| 3 | Catat kondisi barang | Normal, rusak, tidak sesuai |
| 4 | Upload foto (opsional) | Bukti kondisi barang |
| 5 | Submit penerimaan | Status berubah sesuai qty diterima |
| 6 | Stock in otomatis | Stok bertambah di lokasi yang ditentukan |

### Field Penerimaan

| Field | Deskripsi |
| - | - |
| **Quantity Diterima** | Jumlah item yang benar-benar diterima |
| **Quantity Ordered** | Jumlah yang dipesan (reference) |
| **Quantity Outstanding** | Selisih: ordered - received |
| **Kondisi** | Normal / Damaged / Wrong Item |
| **Batch Number** | Nomor batch dari supplier |
| **Expiry Date** | Tanggal kadaluarsa (untuk bahan baku) |
| **Lokasi Tujuan** | Gudang mana barang disimpan |
| **Notes** | Catatan kondisi atau temuan |

## Perbandingan Ordered vs Received

| Item | Ordered | Received | Outstanding | Status |
| - | - | - | - | - |
| Cabai Merah (kg) | 100 | 100 | 0 | Complete |
| Bawang Putih (kg) | 50 | 45 | 5 | Partial |
| Minyak Goreng (liter) | 30 | 30 | 0 | Complete |
| Kemasan Pouch (pcs) | 500 | 0 | 500 | Pending |

## Riwayat & Filter

| Filter | Opsi | Fungsi |
| - | - | - |
| **Status** | Draft, Pending, Approved, Sent, Partial, Full, Rejected, Closed | Filter berdasarkan tahap |
| **Supplier** | Nama supplier | Filter per supplier |
| **Tanggal** | Date range | Filter periode pembuatan PO |
| **Total Value** | Range nilai | Filter berdasarkan nilai PO |

## Integrasi dengan Modul Lain

```mermaid theme={null}
graph LR
    SM[Supplier Management<br/>Data & Harga Supplier] --> PO[Purchase Order]
    PO -->|Stock In| INV[Stock Management<br/>Stok Bertambah]
    PO -->|Invoice| FIN[Keuangan<br/>Hutang & Pembayaran]
    PO -->|PO History| SM
    FIN -->|Budget Check| PO
```

| Modul | Integrasi | Arah |
| - | - | - |
| **Supplier Management** | Data supplier, harga katalog, rating | Supplier → PO |
| **Stock Management** | Stock in otomatis saat barang diterima | PO → Stock |
| **Keuangan** | Hutang usaha, pembayaran, budget tracking | PO → Finance |
| **Manufacturing** | Kebutuhan bahan baku memicu rekomendasi PO | Manufacturing → PO |

## Cetak & Kirim PO

| Aksi | Format | Detail |
| - | - | - |
| **Cetak PDF** | PDF | PO lengkap dengan kop perusahaan, detail item, total |
| **Kirim Email** | Email + PDF attachment | Langsung ke email supplier |
| **WhatsApp** | Text summary | Ringkasan PO via WA |
| **Export CSV** | CSV | Untuk arsip atau integrasi eksternal |

## Tips

* **Gunakan data stok minimum** dari Stock Management sebagai acuan quantity pemesanan — jangan memesan berdasarkan perkiraan.
* **Dokumentasikan setiap penerimaan** dengan foto jika ada barang yang rusak atau tidak sesuai, supaya mudah diklaim ke supplier.
* **Evaluasi supplier** secara berkala berdasarkan ketepatan waktu pengiriman dan kualitas barang yang tercatat di riwayat PO.
* **Manfaatkan partial receiving** — jangan menunggu semua barang datang jika sebagian sudah bisa digunakan untuk produksi.

***

## Entity Relationship Diagram (ERD)

Diagram berikut menggambarkan relasi antar entitas utama yang terlibat dalam alur Purchase Order — mulai dari Supplier sebagai sumber bahan baku, Purchase Order sebagai dokumen pemesanan, StockMovement sebagai pencatatan pergerakan barang, hingga CompanyPOSInventory sebagai audit trail stok di level POS.

```mermaid theme={null}
erDiagram
    Supplier ||--o{ PurchaseOrder : "memiliki (1:N)"
    PurchaseOrder ||--|{ PurchaseOrderItem : "berisi (1:N)"
    PurchaseOrder ||--o{ StockMovement : "memicu stock-in (1:N)"
    StockMovement }o--|| CompanyPOSInventory : "mencatat ke audit trail"
    Supplier ||--o{ StockMovement : "sumber referensi"

    Supplier {
        string company_id PK
        string supplier_code UK "kode unik supplier"
        string company_name "nama perusahaan supplier"
        string contact_person "nama kontak"
        string email "email"
        string phone "telepon"
        string address "alamat"
        string city "kota"
        string country "negara"
        string tax_id "NPWP / Tax ID"
        string bank_account "rekening bank"
        string bank_name "nama bank"
        string payment_terms "syarat pembayaran"
        string currency "mata uang (default IDR)"
        number rating "rating 1-5"
        boolean is_active "status aktif"
        boolean is_locked "terkunci jika ada PO aktif"
        number validation_score "skor kelengkapan 0-100"
        string supplier_type "general|farmer|distributor|importer"
        datetime last_validated_at "terakhir validasi"
        array supplied_materials "daftar bahan yang dipasok"
        string notes "catatan"
    }

    PurchaseOrder {
        string company_id FK
        string po_number UK "nomor PO unik"
        string supplier_id FK
        string supplier_name "nama supplier (denormalized)"
        date po_date "tanggal pembuatan PO"
        date expected_delivery_date "perkiraan tanggal tiba"
        array items "daftar line items"
        number subtotal "subtotal sebelum pajak & ongkir"
        number tax_amount "jumlah pajak"
        number shipping_cost "biaya pengiriman"
        number total_amount "total keseluruhan"
        string status "draft|sent|confirmed|received|invoiced|cancelled"
        string received_status "pending|partially_received|fully_received"
        string notes "catatan PO"
    }

    PurchaseOrderItem {
        string product_id FK
        string product_name "nama produk"
        number quantity "jumlah dipesan"
        string unit "satuan (kg, liter, pcs)"
        number unit_price "harga per unit"
        number total_price "harga x quantity"
        number received_quantity "jumlah diterima (default 0)"
    }

    StockMovement {
        string company_id FK
        string inventory_id FK
        string product_id FK
        string product_sku "SKU produk"
        string product_name "nama produk"
        string movement_type "in|out|transfer|adjustment|return|damaged|hold|hold_release"
        number quantity "jumlah perubahan"
        number stock_before "stok sebelum"
        number stock_after "stok setelah"
        string reference_type "purchase|sale|production|transfer|..."
        string reference_id FK "ID PO/SO/Transfer terkait"
        string lot_id "ID lot/batch untuk traceability"
        string lot_number "nomor lot fisik"
        number unit_cost "harga modal per unit"
        number total_value "quantity x unit_cost"
        string performed_by "user yang melakukan"
        string idempotency_key "retry identity"
    }

    CompanyPOSInventory {
        string company_id FK
        string product_id FK
        string product_name "nama produk"
        string type "in|out|adjustment"
        number quantity "jumlah perubahan"
        number stock_before "stok sebelum"
        number stock_after "stok setelah"
        string reason "alasan perubahan"
        string reference_id FK "ID transaksi terkait"
        string performed_by "user yang melakukan"
        string notes "catatan"
    }
```

### Penjelasan Relasi

| Relasi | Kardinalitas | Deskripsi |
| - | - | - |
| **Supplier → PurchaseOrder** | 1 : N | Satu supplier bisa memiliki banyak PO. Setiap PO wajib merujuk ke satu supplier. |
| **PurchaseOrder → PurchaseOrderItem** | 1 : N | Satu PO berisi minimal satu line item (produk yang dipesan). |
| **PurchaseOrder → StockMovement** | 1 : N | Saat barang diterima, sistem membuat satu atau lebih StockMovement bertipe `in` dengan `reference_type = "purchase"` dan `reference_id`指向 ID PO. |
| **StockMovement → CompanyPOSInventory** | N : 1 | Setiap StockMovement tercatat juga sebagai audit trail di CompanyPOSInventory agar stok di level POS selalu konsisten. |
| **Supplier → StockMovement** | Referensi | StockMovement bisa merujuk supplier sebagai sumber asal barang untuk keperluan traceability. |

***

## Entity Schema Lengkap

### Schema: PurchaseOrder

Berikut adalah seluruh field yang tersimpan di entitas PurchaseOrder, sesuai definisi di `base44/entities/PurchaseOrder.jsonc`.

| Field | Tipe | Wajib | Default | Enum / Format | Deskripsi |
| - | - | - | - | - | - |
| `company_id` | string | **Ya** | — | — | ID perusahaan pemilik PO. Digunakan untuk multi-tenancy dan Row-Level Security (RLS). |
| `po_number` | string | Tidak | — | Format: `PO-{YYYYMMDD}-{seq}` | Nomor PO unik yang di-generate otomatis oleh sistem. Contoh: `PO-20261010-001`. |
| `supplier_id` | string | **Ya** | — | — | ID supplier yang menjadi tujuan pemesanan. Merujuk ke entitas `Supplier`. |
| `supplier_name` | string | Tidak | — | — | Nama supplier (denormalized). Disimpan langsung di PO agar tidak perlu join setiap kali ditampilkan. |
| `po_date` | date | **Ya** | — | ISO 8601 | Tanggal pembuatan PO. Menjadi acuan periode pembelian dan jatuh tempo pembayaran. |
| `expected_delivery_date` | date | Tidak | — | ISO 8601 | Perkiraan tanggal barang tiba dari supplier. Digunakan untuk monitoring ketepatan pengiriman. |
| `items` | array\<object> | **Ya** | — | — | Daftar line item / produk yang dipesan. Setiap item memiliki sub-field: `product_id`, `product_name`, `quantity`, `unit`, `unit_price`, `total_price`, `received_quantity`. |
| `items[].product_id` | string | Ya (per item) | — | — | ID produk yang dipesan. Merujuk ke katalog produk/bahan baku. |
| `items[].product_name` | string | Ya (per item) | — | — | Nama produk (denormalized). |
| `items[].quantity` | number | Ya (per item) | — | — | Jumlah yang dipesan dalam satuan yang tertera. |
| `items[].unit` | string | Ya (per item) | — | — | Satuan: kg, liter, pcs, box, dus, dll. |
| `items[].unit_price` | number | Ya (per item) | — | — | Harga satuan. Bisa di-auto-fill dari katalog harga supplier. |
| `items[].total_price` | number | Ya (per item) | — | — | `quantity × unit_price`. Dihitung otomatis. |
| `items[].received_quantity` | number | Tidak | `0` | — | Jumlah yang sudah diterima. Digunakan untuk menghitung outstanding dan menentukan status `received_status`. |
| `subtotal` | number | Tidak | `0` | — | Jumlah total semua `total_price` sebelum pajak dan ongkir. |
| `tax_amount` | number | Tidak | `0` | — | Jumlah pajak (PPN 11% atau sesuai konfigurasi). |
| `shipping_cost` | number | Tidak | `0` | — | Biaya pengiriman / ongkos kirim. |
| `total_amount` | number | Tidak | — | — | `subtotal + tax_amount + shipping_cost`. Nilai total yang harus dibayarkan. |
| `status` | string | Tidak | `"draft"` | `draft`, `sent`, `confirmed`, `received`, `invoiced`, `cancelled` | Status utama PO dalam lifecycle pemesanan. |
| `received_status` | string | Tidak | `"pending"` | `pending`, `partially_received`, `fully_received` | Status penerimaan barang. Berubah saat user melakukan receiving. |
| `notes` | string | Tidak | — | — | Catatan umum untuk PO (instruksi khusus, permintaan supplier, dll). |

**Required fields:** `company_id`, `supplier_id`, `po_date`, `items`

### Schema: Supplier

Berikut adalah seluruh field yang tersimpan di entitas Supplier, sesuai definisi di `base44/entities/Supplier.jsonc`.

| Field | Tipe | Wajib | Default | Enum / Format | Deskripsi |
| - | - | - | - | - | - |
| `company_id` | string | **Ya** | — | — | ID perusahaan pemilik data supplier. |
| `supplier_code` | string | Tidak | — | — | Kode unik supplier. Di-generate otomatis atau diinput manual. |
| `company_name` | string | **Ya** | — | — | Nama perusahaan supplier. Wajib diisi saat pembuatan. |
| `contact_person` | string | Tidak | — | — | Nama kontak person di supplier. |
| `email` | string | Tidak | — | email format | Alamat email supplier untuk pengiriman PO dan notifikasi. |
| `phone` | string | Tidak | — | — | Nomor telepon / WhatsApp supplier. |
| `address` | string | Tidak | — | — | Alamat lengkap supplier. |
| `city` | string | Tidak | — | — | Kota domisili supplier. |
| `country` | string | Tidak | — | — | Negara (default Indonesia). |
| `tax_id` | string | Tidak | — | — | NPWP atau Tax ID supplier. Diperlukan untuk faktur pajak. |
| `bank_account` | string | Tidak | — | — | Nomor rekening bank supplier. |
| `bank_name` | string | Tidak | — | — | Nama bank tempat rekening supplier. |
| `payment_terms` | string | Tidak | — | `NET 30`, `NET 14`, `NET 7`, `COD`, `CBD` | Syarat pembayaran yang disepakati. |
| `currency` | string | Tidak | `"IDR"` | ISO 4217 | Mata uang transaksi. Default Rupiah (IDR). |
| `rating` | number | Tidak | — | 1 – 5 | Rating supplier berdasarkan kinerja pengiriman dan kualitas barang. |
| `is_active` | boolean | Tidak | `true` | — | Status aktif supplier. Supplier non-aktif tidak muncul di dropdown PO. |
| `is_locked` | boolean | Tidak | `false` | — | Data terkunci karena sudah ada PO aktif. Field nama, NPWP, dan rekening tidak bisa diedit sampai semua PO terkait selesai. |
| `validation_score` | number | Tidak | `0` | 0 – 100 | Skor kelengkapan data supplier. Dihitung otomatis berdasarkan field yang sudah terisi (NPWP, rekening, alamat, kontak, dll). |
| `supplier_type` | string | Tidak | `"general"` | `general`, `farmer`, `distributor`, `importer` | Klasifikasi tipe supplier. Mempengaruhi aturan penerimaan dan dokumen yang diperlukan. |
| `supplied_materials` | array\<string> | Tidak | — | — | Daftar bahan baku / komoditas yang dipasok oleh supplier ini. |
| `last_validated_at` | datetime | Tidak | — | ISO 8601 | Timestamp terakhir data supplier divalidasi. |
| `notes` | string | Tidak | — | — | Catatan tambahan tentang supplier. |

**Required fields:** `company_id`, `company_name`

***

## State Machine — Siklus Hidup Purchase Order

Diagram berikut menunjukkan seluruh transisi status yang valid untuk sebuah Purchase Order, dari pembuatan hingga penutupan. Setiap panah mewakili satu aksi yang bisa dilakukan oleh user atau sistem.

```mermaid theme={null}
stateDiagram-v2
    [*] --> draft: User membuat PO baru

    state "draft" as draft
    state "sent" as sent
    state "confirmed" as confirmed
    state "received" as received
    state "invoiced" as invoiced
    state "cancelled" as cancelled

    state "received_status" as recv {
        [*] --> pending
        pending --> partially_received: Terima sebagian barang
        partially_received --> fully_received: Terima sisa barang
        pending --> fully_received: Terima semua sekaligus
    }

    draft --> sent: Submit & kirim ke supplier
    draft --> cancelled: Batalkan PO (sebelum dikirim)

    sent --> confirmed: Supplier konfirmasi pesanan
    sent --> cancelled: Supplier tolak / batal

    confirmed --> received: Semua barang diterima
    confirmed --> received: Terima parsial → update received_status

    received --> invoiced: Invoice dicatat & diverifikasi
    invoiced --> [*]: Selesai (PO ditutup)

    received --> received: Terima tambahan (jika partial)
    cancelled --> [*]: PO ditutup tanpa fulfillment
```

### Tabel Transisi Status

| Status Awal | Status Tujuan | Pemicu | Kondisi |
| - | - | - | - |
| `[*]` | `draft` | User klik "Buat PO" | Supplier dan minimal 1 item sudah dipilih |
| `draft` | `sent` | User klik "Submit & Kirim" | Validasi: items lengkap, harga terisi, total > 0 |
| `draft` | `cancelled` | User klik "Batalkan" | Belum ada barang dikirim |
| `sent` | `confirmed` | Supplier konfirmasi (manual/otomatis) | PO sudah diterima supplier |
| `sent` | `cancelled` | Supplier tolak / user batalkan | Barang belum dikirim supplier |
| `confirmed` | `received` | User input receiving | Semua `received_quantity` = `quantity` → `received_status = fully_received` |
| `confirmed` | `received` (partial) | User input receiving sebagian | Beberapa item belum diterima → `received_status = partially_received` |
| `received` | `invoiced` | User verifikasi invoice | Cocokkan qty terima dengan invoice supplier |
| `invoiced` | `[*]` | PO ditutup otomatis | Semua item diterima dan invoice terbayar |

### Penjelasan Detail Lifecycle

1. **Draft** — PO masih dalam tahap penyusunan. User bisa menambah, mengubah, atau menghapus item. Belum ada notifikasi ke supplier. Total dihitung otomatis tapi belum final.

2. **Sent** — PO sudah dikirim ke supplier (via email, cetak PDF, atau WhatsApp). Data PO dikunci — tidak bisa diubah lagi. Supplier diharapkan memberikan konfirmasi penerimaan pesanan.

3. **Confirmed** — Supplier telah mengkonfirmasi pesanan. Tahap ini menandakan supplier siap mengirim barang sesuai PO. User mulai bisa melakukan receiving.

4. **Received** — Barang sudah diterima di gudang. Jika semua item diterima lengkap, `received_status` berubah menjadi `fully_received`. Jika sebagian, tetap `partially_received` dan user bisa menerima sisa di kemudian hari. Setiap penerimaan memicu pembuatan record `StockMovement` bertipe `in`.

5. **Invoiced** — Invoice dari supplier sudah dicatat dan diverifikasi. Nilai invoice dicocokkan dengan total PO dan barang yang benar-benar diterima. Tahap ini memicu pencatatan hutang di modul Keuangan.

6. **Cancelled** — PO dibatalkan, bisa karena supplier menolak, barang tidak tersedia, atau keputusan internal. PO yang sudah dibatalkan tidak bisa di-reopen.

***

## Sequence Diagram — Alur Lengkap PO

### 1. Pembuatan PO (Creation)

```mermaid theme={null}
sequenceDiagram
    participant U as User (Staff Gudang)
    participant UI as Frontend
    participant API as Backend API
    participant DB as Database
    participant S as Supplier

    U->>UI: Klik "Buat PO Baru"
    UI->>API: GET /api/suppliers?active=true
    API->>DB: Query supplier aktif
    DB-->>API: Daftar supplier
    API-->>UI: Return supplier list
    U->>UI: Pilih supplier, tambah items
    UI->>API: GET /api/products?search=...
    API-->>UI: Return produk & harga katalog

    loop Tambah setiap item
        U->>UI: Pilih produk, input qty & harga
        UI->>UI: Hitung subtotal per item (qty × harga)
    end

    U->>UI: Review total, isi catatan
    U->>UI: Klik "Simpan Draft"
    UI->>API: POST /api/purchase-orders {supplier_id, items[], po_date, ...}
    API->>API: Validasi: supplier exists, items valid, required fields
    API->>API: Generate po_number: PO-{YYYYMMDD}-{seq}
    API->>API: Hitung subtotal, tax, shipping, total_amount
    API->>DB: INSERT PurchaseOrder (status=draft, received_status=pending)
    DB-->>API: Return created PO
    API-->>UI: Return PO object + po_number
    UI-->>U: Tampilkan PO berhasil dibuat
```

### 2. Approval & Pengiriman ke Supplier

```mermaid theme={null}
sequenceDiagram
    participant U as User (Staff Gudang)
    participant A as Approver (Manager/Owner)
    participant API as Backend API
    participant DB as Database
    participant N as Notification Service
    participant S as Supplier

    U->>API: POST /api/purchase-orders/:id/submit
    API->>DB: UPDATE status = 'sent'
    API->>N: Kirim notifikasi ke supplier
    N->>S: Email / WhatsApp: PO baru terkirim

    Note over S: Supplier review PO

    alt Supplier Konfirmasi
        S->>API: POST /api/purchase-orders/:id/confirm
        API->>DB: UPDATE status = 'confirmed'
        API->>N: Notifikasi ke User: PO dikonfirmasi supplier
        N->>U: "PO-20261010-001 telah dikonfirmasi"
    else Supplier Tolak
        S->>API: POST /api/purchase-orders/:id/cancel
        API->>DB: UPDATE status = 'cancelled'
        API->>N: Notifikasi ke User: PO ditolak supplier
        N->>U: "PO-20261010-001 ditolak oleh supplier"
    end
```

### 3. Penerimaan Barang (Receiving)

```mermaid theme={null}
sequenceDiagram
    participant U as User (Staff Gudang)
    participant UI as Frontend
    participant API as Backend API
    participant DB as Database
    participant SM as StockMovement Service

    U->>UI: Buka PO berstatus "confirmed"
    UI->>API: GET /api/purchase-orders/:id
    API-->>UI: Return PO + items + received quantities

    loop Input penerimaan per item
        U->>UI: Input received_quantity, kondisi, batch number, expiry date
        UI->>UI: Hitung outstanding = ordered - received
    end

    U->>UI: Klik "Simpan Penerimaan"
    UI->>API: POST /api/purchase-orders/:id/receive {items: [{product_id, received_qty, condition, batch, expiry}]}

    API->>API: Validasi: received_qty <= outstanding qty
    API->>DB: UPDATE items[].received_quantity += received_qty

    alt Semua item fully received
        API->>DB: UPDATE received_status = 'fully_received'
    else Masih ada outstanding
        API->>DB: UPDATE received_status = 'partially_received'
    end

    loop Setiap item yang diterima
        API->>SM: Create StockMovement (type=in, reference_type=purchase, reference_id=PO.id)
        SM->>SM: Catat stock_before, stock_after, lot_id, unit_cost
        SM->>DB: INSERT StockMovement
        SM->>DB: UPDATE CompanyPOSInventory (stock_after = stock_before + qty)
    end

    API-->>UI: Return updated PO + StockMovement IDs
    UI-->>U: Tampilkan "Penerimaan berhasil disimpan"
```

### 4. Stock Update & Audit Trail

```mermaid theme={null}
sequenceDiagram
    participant SM as StockMovement Service
    participant INV as Inventory Service
    participant POS as CompanyPOSInventory
    participant DB as Database
    participant AL as StockAlert Service

    SM->>INV: Request: Update stok product_id di location_id
    INV->>DB: SELECT current stock dari inventory
    DB-->>INV: Return stock_before

    INV->>INV: Hitung stock_after = stock_before + quantity

    INV->>DB: UPDATE inventory SET stock = stock_after
    INV->>POS: INSERT audit record {product_id, type=in, quantity, stock_before, stock_after, reason="pembelian", reference_id=PO.id}
    POS->>DB: INSERT CompanyPOSInventory

    INV->>AL: Check: apakah stock_after <= minimum_stock?
    alt Stok di bawah minimum
        AL->>DB: INSERT StockAlert {product_id, type=low_stock, current_stock, minimum_stock}
        AL->>AL: Kirim notifikasi ke User: "Stok [produk] di bawah minimum"
    else Stok aman
        AL->>AL: No action
    end

    SM->>DB: INSERT StockMovement {movement_type=in, reference_type=purchase, lot_id, unit_cost, total_value, ...}
```

***

## Evaluasi & Klasifikasi Supplier

Sistem SNISHOP menyediakan mekanisme evaluasi supplier berbasis skor validasi dan klasifikasi tipe supplier. Kedua aspek ini membantu tim procurement membuat keputusan pembelian yang lebih baik.

### Skor Validasi Supplier (`validation_score`)

Skor validasi dihitung otomatis berdasarkan kelengkapan data supplier. Skor ini membantu mengidentifikasi supplier mana yang datanya masih perlu dilengkapi sebelum bisa diproses secara penuh.

| Komponen Dinilai | Bobot | Keterangan |
| - | - | - |
| **Nama perusahaan** (`company_name`) | 15 poin | Wajib diisi. Tanpa nama perusahaan, supplier tidak bisa digunakan. |
| **Kontak person** (`contact_person`) | 10 poin | Nama PIC yang bisa dihubungi terkait pesanan. |
| **Email** (`email`) | 10 poin | Diperlukan untuk pengiriman PO otomatis via email. |
| **Telepon** (`phone`) | 10 poin | Untuk komunikasi cepat dan konfirmasi pesanan. |
| **Alamat lengkap** (`address` + `city`) | 10 poin | Diperlukan untuk kalkulasi ongkos kirim dan visit. |
| **NPWP / Tax ID** (`tax_id`) | 15 poin | Diperlukan untuk penerbitan faktur pajak dan laporan SPT. |
| **Rekening bank** (`bank_account` + `bank_name`) | 15 poin | Diperlukan untuk transfer pembayaran. |
| **Syarat pembayaran** (`payment_terms`) | 5 poin | Membantu sistem menghitung jatuh tempo otomatis. |
| **Tipe supplier** (`supplier_type`) | 5 poin | Klasifikasi untuk aturan penerimaan yang berbeda. |
| **Bahan yang dipasok** (`supplied_materials`) | 5 poin | Memudahkan pencarian supplier berdasarkan bahan baku. |
| **TOTAL MAKSIMAL** | **100 poin** | Skor 100 = data supplier lengkap dan siap digunakan. |

### Interpretasi Skor

| Range Skor | Kategori | Tindakan yang Disarankan |
| - | - | - |
| **80 – 100** | Lengkap | Supplier siap digunakan untuk PO. Semua data penting terisi. |
| **60 – 79** | Cukup | Bisa digunakan, tapi lengkapi field yang kosong sebelum PO pertama. |
| **40 – 59** | Kurang | Prioritaskan melengkapi data. Hubungi supplier untuk minta informasi yang hilang. |
| **0 – 39** | Tidak lengkap | **Jangan buat PO** dari supplier ini. Data terlalu berisiko untuk transaksi. |

### Klasifikasi Tipe Supplier (`supplier_type`)

Tipe supplier menentukan aturan penerimaan dan dokumen yang diperlukan saat barang tiba di gudang.

| Tipe | Kode | Deskripsi | Aturan Penerimaan Khusus |
| - | - | - | - |
| **General** | `general` | Supplier umum / toko bahan baku | Penerimaan standar. Cek qty dan kondisi visual. |
| **Farmer** | `farmer` | Petani / produsen langsung | Cek kualitas bahan segar (kesegaran, kadar air, ukuran). Wajib input `expiry_date` dan `batch_number`. Toleransi berat +/- 5%. |
| **Distributor** | `distributor` | Distributor / agen resmi | Cek kemasan utuh, label, dan sertifikat halal/BPOM jika diperlukan. Batch number wajib dicatat untuk traceability. |
| **Importer** | `importer` | Importir bahan baku luar negeri | Cek dokumen Bea Cukai, sertifikat asal barang (COO), dan label impor. Wajib verifikasi kesesuaian dengan spesifikasi yang disepakati. |

### Matriks Evaluasi Supplier Berkala

Selain skor validasi, evaluasi berkala dilakukan berdasarkan kinerja nyata supplier yang tercatat di riwayat PO.

| Kriteria | Bobot | Cara Hitung | Target |
| - | - | - | - |
| **Ketepatan Waktu** | 30% | % PO yang tiba sesuai `expected_delivery_date` | >= 85% |
| **Kualitas Barang** | 25% | % penerimaan dengan kondisi "normal" (tidak rusak/salah) | >= 95% |
| **Kelengkapan Qty** | 20% | % PO yang diterima fully tanpa outstanding | >= 90% |
| **Responsivitas** | 15% | Rata-rata waktu konfirmasi PO setelah dikirim | \< 24 jam |
| **Harga Kompetitif** | 10% | Perbandingan harga vs rata-rata pasar untuk bahan yang sama | Dalam range +/- 5% |

***

## Schema: StockMovement (Detail Lengkap)

Entitas StockMovement adalah tulang punggung audit trail stok. Setiap kali stok berubah — baik karena pembelian, penjualan, produksi, transfer, atau adjustment — satu record StockMovement dibuat.

| Field | Tipe | Wajib | Default | Enum / Format | Deskripsi |
| - | - | - | - | - | - |
| `company_id` | string | **Ya** | — | — | ID perusahaan. Untuk multi-tenancy. |
| `inventory_id` | string | **Ya** | — | — | ID record inventory yang berubah. |
| `product_id` | string | Tidak | — | — | ID produk. |
| `product_sku` | string | Tidak | — | — | SKU produk untuk identifikasi cepat. |
| `product_type` | string | Tidak | — | — | Tipe produk (bahan baku, produk jadi, kemasan). |
| `identity_status` | string | Tidak | — | `linked`, `legacy_review` | Status identitas produk. `linked` = sudah terhubung ke katalog. `legacy_review` = perlu review manual. |
| `source_material_id` | string | Tidak | — | — | ID material asal (untuk traceability bahan baku). |
| `product_name` | string | Tidak | — | — | Nama produk (denormalized). |
| `category_key` | string | Tidak | — | — | Key kategori produk. |
| `category_name` | string | Tidak | — | — | Nama kategori (denormalized). |
| `variant_key` | string | Tidak | — | — | Key varian produk. |
| `variant_name` | string | Tidak | — | — | Nama varian (denormalized). |
| `base_product_key` | string | Tidak | — | — | Key produk dasar (untuk produk dengan varian). |
| `variant_label` | string | Tidak | — | — | Label varian (misalnya "Ukuran Besar", "Rasa Original"). |
| `size_grams` | number | Tidak | — | — | Ukuran produk dalam gram (untuk produk F\&B). |
| `location_id` | string | Tidak | — | — | ID lokasi gudang tempat stok berada. |
| `location_name` | string | Tidak | — | — | Nama lokasi (denormalized). |
| `movement_type` | string | **Ya** | — | `in`, `out`, `transfer`, `adjustment`, `return`, `damaged`, `hold`, `hold_release` | Tipe pergerakan stok. `hold` dan `hold_release` bersifat informasional (blokir lot untuk QC) dan tidak mengubah jumlah on-hand. |
| `quantity` | number | **Ya** | — | — | Jumlah perubahan stok. Positif untuk `in`, negatif untuk `out`. |
| `stock_before` | number | Tidak | — | — | Jumlah stok sebelum pergerakan. |
| `stock_after` | number | Tidak | — | — | Jumlah stok setelah pergerakan. |
| `reference_type` | string | Tidak | — | `purchase`, `sale`, `cashier_sale`, `production`, `raw_material_outbound`, `qc_release`, `transfer`, `adjustment`, `opname_adjustment`, `return`, `manual`, `distribution_shipment`, `quality_hold` | Sumber / penyebab perubahan stok. Untuk PO yang diterima, nilainya `purchase`. |
| `lot_id` | string | Tidak | — | — | ID lot/batch untuk traceability. Penting untuk produk F\&B yang perlu tracking tanggal kadaluarsa. |
| `lot_number` | string | Tidak | — | — | Nomor label lot/batch fisik pada kemasan. |
| `reference_id` | string | Tidak | — | — | ID transaksi terkait. Untuk penerimaan PO, field ini berisi ID PurchaseOrder. |
| `from_location_id` | string | Tidak | — | — | Lokasi asal (untuk movement tipe `transfer`). |
| `to_location_id` | string | Tidak | — | — | Lokasi tujuan (untuk movement tipe `transfer`). |
| `reason` | string | Tidak | — | — | Alasan perubahan stok. |
| `override_reason` | string | Tidak | — | — | Alasan jika pemilihan lot menyimpang dari urutan FIFO standar (rule FIFO-03). |
| `allocation_strategy` | string | Tidak | — | — | Strategi alokasi yang digunakan: `fifo`, `fefo`, atau `manual`. |
| `unit_cost` | number | Tidak | `0` | — | Harga modal per unit saat transaksi terjadi. |
| `total_value` | number | Tidak | `0` | — | `quantity × unit_cost` — nilai modal total. |
| `sales_channel` | string | Tidak | — | — | Nama channel penjualan (Shopee, TikTok, WhatsApp, Indomaret, B2B, dll). |
| `channel_price_key` | string | Tidak | — | — | Key pricing channel dari CompanyPOSProduct. |
| `unit_price` | number | Tidak | `0` | — | Harga jual per unit (dinamis per channel). |
| `total_revenue` | number | Tidak | `0` | — | `quantity × unit_price` — pendapatan penjualan. |
| `total_cost` | number | Tidak | `0` | — | `quantity × unit_cost` — total modal. |
| `profit` | number | Tidak | `0` | — | `total_revenue - total_cost` — laba/rugi. |
| `profit_margin` | number | Tidak | `0` | — | `profit / total_revenue × 100` — margin laba dalam persen. |
| `performed_by` | string | Tidak | — | — | Email user yang melakukan pergerakan stok. |
| `performed_by_name` | string | Tidak | — | — | Nama lengkap user (denormalized). |
| `notes` | string | Tidak | — | — | Catatan tambahan. |
| `idempotency_key` | string | Tidak | — | — | Retry identity untuk mencegah duplikasi pergerakan stok. |
| `created_by` | string | Tidak | — | — | Authenticated actor ID. |
| `metadata` | object | Tidak | — | — | Command correlation metadata untuk audit trail. |

**Required fields:** `company_id`, `inventory_id`, `movement_type`, `quantity`

### Tipe Pergerakan Stok (`movement_type`)

| Tipe | Pengaruh pada Stok | Deskripsi |
| - | - | - |
| `in` | Menambah | Barang masuk — dari pembelian PO, retur penjualan, atau produksi. |
| `out` | Mengurangi | Barang keluar — untuk penjualan, produksi (bahan baku terpakai), atau distribusi. |
| `transfer` | Netral | Perpindahan antar lokasi gudang. Stok di lokasi asal berkurang, di lokasi tujuan bertambah. |
| `adjustment` | +/- | Penyesuaian manual — hasil stock opname atau koreksi selisih. |
| `return` | Menambah | Retur barang dari supplier atau retur penjualan dari customer. |
| `damaged` | Mengurangi | Barang rusak / tidak bisa dijual. Stok berkurang tapi tetap tercatat untuk audit. |
| `hold` | **Netral** | Blokir lot untuk Quality Control. Stok on-hand tidak berubah, tapi lot ditandai sebagai "ditahan". |
| `hold_release` | **Netral** | Lepaskan blokir lot setelah QC selesai. Lot kembali tersedia untuk digunakan. |

***

## Schema: CompanyPOSInventory (Detail Lengkap)

Entitas ini berfungsi sebagai audit trail stok di level POS (Point of Sale). Setiap perubahan stok tercatat di sini agar sistem POS selalu memiliki data stok real-time.

| Field | Tipe | Wajib | Default | Enum | Deskripsi |
| - | - | - | - | - | - |
| `company_id` | string | **Ya** | — | — | ID perusahaan. |
| `product_id` | string | **Ya** | — | — | ID produk. |
| `product_name` | string | Tidak | — | — | Nama produk (denormalized). |
| `type` | string | **Ya** | — | `in`, `out`, `adjustment` | Tipe pergerakan: masuk, keluar, atau penyesuaian. |
| `quantity` | number | **Ya** | — | — | Jumlah perubahan stok. |
| `stock_before` | number | Tidak | — | — | Stok sebelum perubahan. |
| `stock_after` | number | Tidak | — | — | Stok setelah perubahan. |
| `reason` | string | Tidak | — | — | Alasan perubahan (pembelian, penjualan, rusak, dll). |
| `reference_id` | string | Tidak | — | — | ID transaksi terkait (PO, SO, Transfer). |
| `notes` | string | Tidak | — | — | Catatan tambahan. |
| `performed_by` | string | Tidak | — | — | User yang melakukan perubahan. |

**Required fields:** `company_id`, `product_id`, `type`, `quantity`

***

## Ringkasan Alur Data End-to-End

Diagram berikut merangkum bagaimana data mengalir dari pembuatan PO hingga stok terupdate di sistem POS:

```mermaid theme={null}
flowchart TB
    subgraph "1. Pembuatan PO"
        A1[User buat PO] --> A2[Pilih Supplier]
        A2 --> A3[Tambah Items<br/>produk, qty, harga]
        A3 --> A4[Simpan Draft<br/>status=draft]
    end

    subgraph "2. Pengiriman"
        B1[Submit PO] --> B2[status=sent]
        B2 --> B3[Kirim ke Supplier<br/>email / PDF / WA]
    end

    subgraph "3. Konfirmasi & Penerimaan"
        C1[Supplier konfirmasi<br/>status=confirmed] --> C2[User terima barang]
        C2 --> C3{Semua item<br/>diterima?}
        C3 -->|Ya| C4[received_status=fully_received]
        C3 -->|Sebagian| C5[received_status=partially_received]
        C5 --> C2
    end

    subgraph "4. Stock Update"
        D1[Create StockMovement<br/>type=in, ref_type=purchase] --> D2[Update Inventory<br/>stock_after = stock_before + qty]
        D2 --> D3[Update CompanyPOSInventory<br/>audit trail POS]
        D3 --> D4{Stok <= minimum?}
        D4 -->|Ya| D5[Create StockAlert]
        D4 -->|Tidak| D6[Selesai]
    end

    subgraph "5. Invoice & Pembayaran"
        E1[Verifikasi Invoice] --> E2[status=invoiced]
        E2 --> E3[Catat Hutang<br/>di modul Keuangan]
        E3 --> E4[Pembayaran]
        E4 --> E5[PO ditutup]
    end

    A4 --> B1
    B3 --> C1
    C4 --> D1
    C4 --> E1
```

### Catatan Implementasi

* **Denormalisasi**: Field seperti `supplier_name`, `product_name`, `location_name` disimpan langsung di entitas terkait untuk menghindari join berulang saat query. Ini trade-off antara konsistensi data dan performa baca.
* **Idempotency**: Setiap StockMovement memiliki `idempotency_key` untuk mencegah duplikasi jika ada retry pada proses penerimaan barang.
* **Row-Level Security (RLS)**: Semua entitas menggunakan RLS berdasarkan `company_id` untuk memastikan setiap perusahaan hanya melihat datanya sendiri.
* **Traceability Lot**: Field `lot_id` dan `lot_number` di StockMovement memungkinkan pelacakan batch dari supplier hingga ke produk jadi — penting untuk standar HACCP dan recall produk.
* **FIFO/FEFO**: Strategi alokasi (`fifo` atau `fefo`) dicatat di setiap StockMovement keluar untuk audit kepatuhan terhadap prosedur pengeluaran stok.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.