﻿# GWin — Warehouse & Inventory Management: Schema & Module Planning Guide

> **Aplikasi:** `gwin` — Warehouse & Inventory  
> **Konteks:** Manajemen aset lab (alat sampling, alat lab), bahan habis pakai, bahan kimia, dan kode aset  
> **Arsitektur:** Laravel Modular Monolith + DDD + CQRS (sesuai `ARCHITECTURE_GUIDE.md`)

---

## Konvensi Tabel

| Prefix | Kegunaan | Contoh |
|--------|----------|--------|
| `gwin_m_*` | Master data | `gwin_m_items` |
| `gwin_t_*` | Transaksi | `gwin_t_stock_receipts` |
| `gwin_h_*` | History / log | `gwin_h_stock_movements` |
| `gwin_cfg_*` | Konfigurasi | `gwin_cfg_item_categories` |
| `gwin_p_*` | Pivot / relasi M:M | `gwin_p_receipt_items` |

**Kolom standar wajib di semua tabel:**
```sql
id              BIGINT UNSIGNED AUTO_INCREMENT PK
uuid            UUID UNIQUE
-- ... kolom bisnis ...
created_by      BIGINT UNSIGNED NULL
updated_by      BIGINT UNSIGNED NULL
deleted_by      BIGINT UNSIGNED NULL
updated_ip      VARCHAR(45) NULL
created_at      TIMESTAMP NULL
updated_at      TIMESTAMP NULL
deleted_at      TIMESTAMP NULL  -- soft delete
```

---

## Daftar Module

```
Phase 1 — Foundation (Master Data & Konfigurasi)
Phase 2 — Asset Management (Alat Lab & Sampling)
Phase 3 — Inventory/Stock (Bahan Habis Pakai & Kimia)
Phase 4 — Transaksi Gudang (Penerimaan, Pengeluaran, Transfer)
Phase 5 — Stock Control (Opname, Penyesuaian, Alert)
Phase 6 — Chemical Safety (MSDS, Expiry, B3)
Phase 7 — Reporting
```

---

## Phase 1 — Foundation (Master Data)

### Module: `UnitOfMeasure`

```sql
-- gwin_cfg_units_of_measure
id, uuid
code            VARCHAR(20) UNIQUE NOT NULL     -- 'PCS', 'LITER', 'KG', 'BOX'
name            VARCHAR(100) NOT NULL
type            ENUM('unit','volume','weight','length','area','other')
is_active       BOOLEAN DEFAULT true
[standard cols]

-- gwin_cfg_uom_conversions  (konversi antar satuan)
id, uuid
from_uom_id     BIGINT UNSIGNED FK → gwin_cfg_units_of_measure
to_uom_id       BIGINT UNSIGNED FK → gwin_cfg_units_of_measure
factor          DECIMAL(18,6) NOT NULL      -- 1 BOX = 12 PCS → factor = 12
[standard cols]
UNIQUE(from_uom_id, to_uom_id)
```

---

### Module: `ItemCategory`

```sql
-- gwin_cfg_item_categories
id, uuid
code            VARCHAR(30) UNIQUE NOT NULL
name            VARCHAR(100) NOT NULL
type            ENUM(
                    'lab_equipment',        -- alat lab (neraca, pH meter, dll)
                    'sampling_equipment',   -- alat sampling (botol, jarum, dll)
                    'consumable',           -- bahan habis pakai (kertas filter, tip, dll)
                    'chemical',             -- bahan kimia
                    'glassware',            -- peralatan gelas (beaker, labu, dll)
                    'ppe',                  -- APD (sarung tangan, masker, dll)
                    'spare_part',           -- suku cadang alat
                    'office_supply',        -- ATK & kebutuhan kantor
                    'other'
                )
is_asset        BOOLEAN DEFAULT false       -- true = masuk asset register (bukan stock biasa)
parent_id       BIGINT UNSIGNED NULL FK     -- hierarki kategori
requires_msds   BOOLEAN DEFAULT false       -- true = wajib input MSDS (bahan kimia/B3)
requires_expiry BOOLEAN DEFAULT false       -- true = wajib input expired date
is_active       BOOLEAN DEFAULT true
[standard cols]
```

---

### Module: `Warehouse`

```sql
-- gwin_m_warehouses
id, uuid
code            VARCHAR(30) UNIQUE NOT NULL
name            VARCHAR(200) NOT NULL
type            ENUM('main','lab','field','transit','disposal')
address         TEXT NULL
pic_user_id     BIGINT UNSIGNED NULL        -- penanggung jawab gudang
is_active       BOOLEAN DEFAULT true
[standard cols]

-- gwin_m_warehouse_locations  (rak/lemari/area dalam gudang)
id, uuid
warehouse_id    BIGINT UNSIGNED FK → gwin_m_warehouses
code            VARCHAR(30) UNIQUE NOT NULL
name            VARCHAR(100) NOT NULL       -- 'Rak A1', 'Lemari Kimia B', 'Kulkas 1'
type            ENUM('shelf','cabinet','refrigerator','freezer','area','other')
temperature_min DECIMAL(5,2) NULL           -- untuk bahan yang perlu suhu tertentu
temperature_max DECIMAL(5,2) NULL
is_active       BOOLEAN DEFAULT true
[standard cols]
```

---

### Module: `Item` (Katalog Barang)

```sql
-- gwin_m_items
id, uuid
code            VARCHAR(50) UNIQUE NOT NULL
name            VARCHAR(300) NOT NULL
item_category_id BIGINT UNSIGNED FK → gwin_cfg_item_categories
brand           VARCHAR(100) NULL
model_number    VARCHAR(100) NULL           -- model/tipe produk
unit_of_measure_id BIGINT UNSIGNED FK → gwin_cfg_units_of_measure
purchase_uom_id BIGINT UNSIGNED NULL FK     -- satuan beli (bisa beda dg satuan stok)
description     TEXT NULL
specification   TEXT NULL                   -- spesifikasi teknis
image_path      VARCHAR(500) NULL
barcode         VARCHAR(100) NULL UNIQUE
minimum_stock   DECIMAL(18,4) DEFAULT 0     -- stok minimum alert
reorder_point   DECIMAL(18,4) DEFAULT 0     -- titik reorder
shelf_life_days INT NULL                    -- umur simpan (untuk bahan kimia/consumable)
storage_condition VARCHAR(200) NULL          -- 'Simpan di suhu 2-8°C', 'Jauhkan dari cahaya'
is_asset        BOOLEAN DEFAULT false        -- sync dengan item_category.is_asset
is_active       BOOLEAN DEFAULT true
notes           TEXT NULL
[standard cols]
```

---

### Module: `NumberingSequence`

**Tujuan:** Auto-numbering untuk semua dokumen gudang.

```sql
-- gwin_cfg_numbering_sequences
id, uuid
document_type       VARCHAR(50) UNIQUE NOT NULL  -- 'stock_receipt', 'stock_issue', 'asset_code', dll
prefix              VARCHAR(20) NOT NULL          -- 'GR', 'GI', 'TR'
format              VARCHAR(100) NOT NULL         -- '{PREFIX}/{YEAR}/{MONTH}/{SEQ:5}'
year_reset          BOOLEAN DEFAULT true
last_sequence       INT DEFAULT 0
current_year        SMALLINT NULL
example             VARCHAR(100) NULL
is_active           BOOLEAN DEFAULT true
[standard cols]
```

---

## Phase 2 — Asset Management (Alat Lab & Sampling)

### Module: `AssetRegister`

**Tujuan:** Setiap alat lab / alat sampling yang bernilai dan perlu dilacak memiliki **kode aset unik** (bisa scan barcode/QR).

```sql
-- gwin_m_assets
id, uuid
asset_code          VARCHAR(50) UNIQUE NOT NULL     -- kode aset unik, bisa di-generate
item_id             BIGINT UNSIGNED FK → gwin_m_items
serial_number       VARCHAR(100) NULL UNIQUE
brand               VARCHAR(100) NULL
model_number        VARCHAR(100) NULL
acquisition_date    DATE NULL
acquisition_cost    DECIMAL(18,2) NULL
condition           ENUM('good','minor_damage','major_damage','under_repair','scrapped') DEFAULT 'good'
status              ENUM('available','in_use','loan','maintenance','calibration','disposed') DEFAULT 'available'
warehouse_id        BIGINT UNSIGNED NULL FK → gwin_m_warehouses
location_id         BIGINT UNSIGNED NULL FK → gwin_m_warehouse_locations
current_holder_id   BIGINT UNSIGNED NULL            -- user yang sedang memegang
current_holder_type VARCHAR(50) NULL                -- 'user', 'department', 'lab'
-- integrasi ke gfin Fixed Asset
fin_asset_id        BIGINT UNSIGNED NULL            -- referensi ke finance_m_fixed_assets
-- kalibrasi / sertifikasi
next_calibration_date   DATE NULL
calibration_interval_days INT NULL                  -- interval kalibrasi dalam hari
calibration_certificate VARCHAR(500) NULL           -- path file sertifikat
last_calibrated_at  DATE NULL
-- Barcode / QR
qr_code_path        VARCHAR(500) NULL
notes               TEXT NULL
[standard cols]

-- gwin_h_asset_code_logs  (history perubahan kode aset)
id, uuid
asset_id            BIGINT UNSIGNED FK → gwin_m_assets
old_code            VARCHAR(50) NULL
new_code            VARCHAR(50) NOT NULL
reason              VARCHAR(300) NULL
changed_by          BIGINT UNSIGNED
created_at          TIMESTAMP
```

---

### Module: `AssetCodeManagement`

**Tujuan:** Generate dan kelola format kode aset secara sistematis.

```sql
-- gwin_cfg_asset_code_formats
id, uuid
item_category_id    BIGINT UNSIGNED FK → gwin_cfg_item_categories
prefix              VARCHAR(10) NOT NULL        -- 'ALA' = Alat Lab, 'SAM' = Sampling
format              VARCHAR(50) NOT NULL        -- '{PREFIX}-{YEAR}-{SEQ:4}'
last_sequence       INT DEFAULT 0
example             VARCHAR(50) NULL            -- 'ALA-2026-0001'
is_active           BOOLEAN DEFAULT true
[standard cols]

-- Contoh format hasil generate:
-- Alat Lab     → ALA-2026-0001
-- Alat Sampling → SAM-2026-0023
-- Glassware    → GLS-2026-0010
-- PPE          → PPE-2026-0005
```

**Logic generate kode (di Domain Service):**
```
AssetCodeGeneratorService::generate(ItemCategory $category, int $year): string
    → ambil format dari gwin_cfg_asset_code_formats
    → increment last_sequence (atomic, pakai DB transaction)
    → return formatted code: 'ALA-2026-0042'
```

---

### Module: `AssetAssignment`

**Tujuan:** Tracking peminjaman / penugasan aset ke user atau divisi.

```sql
-- gwin_t_asset_assignments
id, uuid
asset_id            BIGINT UNSIGNED FK → gwin_m_assets
assigned_to_id      BIGINT UNSIGNED NOT NULL    -- user id
assigned_to_name    VARCHAR(200) NOT NULL        -- snapshot nama
department          VARCHAR(100) NULL
purpose             TEXT NULL
assigned_date       DATE NOT NULL
expected_return_date DATE NULL
actual_return_date  DATE NULL
status              ENUM('active','returned','overdue') DEFAULT 'active'
condition_on_assign ENUM('good','minor_damage','major_damage') DEFAULT 'good'
condition_on_return ENUM('good','minor_damage','major_damage') NULL
assigned_by         BIGINT UNSIGNED NULL         -- siapa yang menyetujui peminjaman
notes_assign        TEXT NULL
notes_return        TEXT NULL
returned_by         BIGINT UNSIGNED NULL
[standard cols]
```

---

### Module: `AssetMaintenance`

**Tujuan:** Jadwal dan histori maintenance + kalibrasi alat.

```sql
-- gwin_t_asset_maintenance
id, uuid
asset_id            BIGINT UNSIGNED FK → gwin_m_assets
type                ENUM('preventive','corrective','calibration','inspection','cleaning')
scheduled_date      DATE NULL
actual_date         DATE NULL
technician          VARCHAR(200) NULL       -- nama teknisi / vendor service
vendor              VARCHAR(200) NULL
cost                DECIMAL(18,2) NULL
description         TEXT NULL
findings            TEXT NULL
action_taken        TEXT NULL
next_schedule_date  DATE NULL
certificate_no      VARCHAR(100) NULL       -- untuk kalibrasi: nomor sertifikat
certificate_path    VARCHAR(500) NULL
status              ENUM('scheduled','in_progress','completed','cancelled') DEFAULT 'scheduled'
[standard cols]
```

---

## Phase 3 — Inventory / Stock

### Module: `StockLedger`

**Tujuan:** Saldo stok real-time per item per gudang per lokasi.

```sql
-- gwin_m_stock_ledger  (current balance — always updated)
id, uuid
item_id             BIGINT UNSIGNED FK → gwin_m_items
warehouse_id        BIGINT UNSIGNED FK → gwin_m_warehouses
location_id         BIGINT UNSIGNED NULL FK → gwin_m_warehouse_locations
quantity_on_hand    DECIMAL(18,4) DEFAULT 0
quantity_reserved   DECIMAL(18,4) DEFAULT 0     -- dialokasikan tapi belum keluar
quantity_available  DECIMAL(18,4) GENERATED ALWAYS AS (quantity_on_hand - quantity_reserved)
average_cost        DECIMAL(18,6) DEFAULT 0     -- moving average cost
last_transaction_at TIMESTAMP NULL
[standard cols]
UNIQUE(item_id, warehouse_id, location_id)

-- gwin_h_stock_movements  (setiap pergerakan stok — TIDAK PERNAH dihapus)
id, uuid
item_id             BIGINT UNSIGNED FK → gwin_m_items
warehouse_id        BIGINT UNSIGNED FK → gwin_m_warehouses
location_id         BIGINT UNSIGNED NULL FK
movement_type       ENUM(
                        'receipt',          -- penerimaan dari supplier
                        'issue',            -- pengeluaran ke lab/user
                        'transfer_in',      -- pindah masuk dari gudang lain
                        'transfer_out',     -- pindah keluar ke gudang lain
                        'adjustment_plus',  -- penyesuaian tambah
                        'adjustment_minus', -- penyesuaian kurang
                        'opname',           -- koreksi stok opname
                        'return_in',        -- retur dari lab/user kembali ke gudang
                        'return_out'        -- retur ke supplier
                    )
reference_type      VARCHAR(50) NOT NULL     -- 'stock_receipt', 'stock_issue', dll
reference_id        BIGINT UNSIGNED NOT NULL -- id transaksi sumber
quantity_before     DECIMAL(18,4)
quantity_change     DECIMAL(18,4)            -- positif = masuk, negatif = keluar
quantity_after      DECIMAL(18,4)
unit_cost           DECIMAL(18,6) NULL
total_cost          DECIMAL(18,2) NULL
batch_number        VARCHAR(100) NULL
expiry_date         DATE NULL
notes               TEXT NULL
created_by          BIGINT UNSIGNED
created_at          TIMESTAMP
```

---

### Module: `StockBatch`

**Tujuan:** Tracking batch/lot untuk bahan kimia dan consumable (FIFO/FEFO).

```sql
-- gwin_m_stock_batches
id, uuid
item_id             BIGINT UNSIGNED FK → gwin_m_items
warehouse_id        BIGINT UNSIGNED FK → gwin_m_warehouses
location_id         BIGINT UNSIGNED NULL FK
batch_number        VARCHAR(100) NOT NULL
lot_number          VARCHAR(100) NULL
manufacture_date    DATE NULL
expiry_date         DATE NULL
quantity_received   DECIMAL(18,4)
quantity_on_hand    DECIMAL(18,4)
quantity_reserved   DECIMAL(18,4) DEFAULT 0
unit_cost           DECIMAL(18,6) DEFAULT 0
supplier_id         BIGINT UNSIGNED NULL         -- referensi ke gfin.Vendor
purchase_invoice_id BIGINT UNSIGNED NULL         -- referensi ke gfin.PurchaseInvoice
stock_receipt_line_id BIGINT UNSIGNED NULL FK → gwin_t_stock_receipt_lines  -- batch dibuat dari receipt line ini
status              ENUM('active','exhausted','expired','quarantine') DEFAULT 'active'
notes               TEXT NULL
[standard cols]
UNIQUE(item_id, batch_number, warehouse_id)
```

---

## Phase 4 — Transaksi Gudang

### Module: `StockReceipt` (Penerimaan Barang)

```sql
-- gwin_t_stock_receipts
id, uuid
receipt_number      VARCHAR(50) UNIQUE NOT NULL
warehouse_id        BIGINT UNSIGNED FK → gwin_m_warehouses
receipt_date        DATE NOT NULL
source_type         ENUM('purchase','return_from_lab','transfer','opening_balance','other')
supplier_id         BIGINT UNSIGNED NULL         -- referensi ke gfin.Vendor
supplier_name       VARCHAR(200) NULL            -- snapshot nama (fallback jika belum link ke gfin)
purchase_order_no   VARCHAR(100) NULL
delivery_note_no    VARCHAR(100) NULL
purchase_invoice_id BIGINT UNSIGNED NULL         -- link ke gfin.PurchaseInvoice
status              ENUM('draft','confirmed','cancelled') DEFAULT 'draft'
confirmed_at        TIMESTAMP NULL
confirmed_by        BIGINT UNSIGNED NULL
notes               TEXT NULL
[standard cols]

-- gwin_t_stock_receipt_lines
id, uuid
stock_receipt_id    BIGINT UNSIGNED FK → gwin_t_stock_receipts
item_id             BIGINT UNSIGNED FK → gwin_m_items
location_id         BIGINT UNSIGNED NULL FK → gwin_m_warehouse_locations
qty_ordered         DECIMAL(18,4) NULL
qty_received        DECIMAL(18,4) NOT NULL
uom_id              BIGINT UNSIGNED FK → gwin_cfg_units_of_measure
unit_cost           DECIMAL(18,6) DEFAULT 0
total_cost          DECIMAL(18,2) DEFAULT 0
batch_number        VARCHAR(100) NULL
expiry_date         DATE NULL
manufacture_date    DATE NULL
condition           ENUM('good','damaged','quarantine') DEFAULT 'good'
notes               TEXT NULL
[standard cols]
```

---

### Module: `StockIssue` (Pengeluaran Barang)

```sql
-- gwin_t_stock_issues
id, uuid
issue_number        VARCHAR(50) UNIQUE NOT NULL
warehouse_id        BIGINT UNSIGNED FK → gwin_m_warehouses
issue_date          DATE NOT NULL
destination_type    ENUM('lab_use','sampling','maintenance','disposal','other')
requested_by_id     BIGINT UNSIGNED NOT NULL     -- user pemohon
department          VARCHAR(100) NULL
-- referensi ke glims (jika pengeluaran untuk pengujian)
glims_work_order_id BIGINT UNSIGNED NULL
glims_order_type    VARCHAR(50) NULL             -- 'lhu', 'contract'
status              ENUM('draft','approved','issued','cancelled') DEFAULT 'draft'
approved_by         BIGINT UNSIGNED NULL
approved_at         TIMESTAMP NULL
issued_by           BIGINT UNSIGNED NULL
issued_at           TIMESTAMP NULL
notes               TEXT NULL
[standard cols]

-- gwin_t_stock_issue_lines
id, uuid
stock_issue_id      BIGINT UNSIGNED FK → gwin_t_stock_issues
item_id             BIGINT UNSIGNED FK → gwin_m_items
location_id         BIGINT UNSIGNED NULL FK
batch_id            BIGINT UNSIGNED NULL FK → gwin_m_stock_batches
qty_requested       DECIMAL(18,4)
qty_issued          DECIMAL(18,4) NOT NULL
uom_id              BIGINT UNSIGNED FK
unit_cost           DECIMAL(18,6) DEFAULT 0
total_cost          DECIMAL(18,2) DEFAULT 0
notes               TEXT NULL
[standard cols]
```

---

### Module: `StockTransfer` (Transfer Antar Gudang)

```sql
-- gwin_t_stock_transfers
id, uuid
transfer_number     VARCHAR(50) UNIQUE NOT NULL
from_warehouse_id   BIGINT UNSIGNED FK → gwin_m_warehouses
to_warehouse_id     BIGINT UNSIGNED FK → gwin_m_warehouses
transfer_date       DATE NOT NULL
status              ENUM('draft','in_transit','received','cancelled') DEFAULT 'draft'
dispatched_by       BIGINT UNSIGNED NULL
dispatched_at       TIMESTAMP NULL
received_by         BIGINT UNSIGNED NULL
received_at         TIMESTAMP NULL
notes               TEXT NULL
[standard cols]

-- gwin_t_stock_transfer_lines
id, uuid
stock_transfer_id   BIGINT UNSIGNED FK → gwin_t_stock_transfers
item_id             BIGINT UNSIGNED FK → gwin_m_items
from_location_id    BIGINT UNSIGNED NULL FK
to_location_id      BIGINT UNSIGNED NULL FK
batch_id            BIGINT UNSIGNED NULL FK → gwin_m_stock_batches
qty_transferred     DECIMAL(18,4) NOT NULL
qty_received        DECIMAL(18,4) NULL          -- diisi saat diterima (bisa beda)
uom_id              BIGINT UNSIGNED FK
notes               TEXT NULL
[standard cols]
```

---

### Module: `StockReturn` (Retur ke Supplier)

```sql
-- gwin_t_stock_returns
id, uuid
return_number       VARCHAR(50) UNIQUE NOT NULL
warehouse_id        BIGINT UNSIGNED FK → gwin_m_warehouses
return_date         DATE NOT NULL
supplier_id         BIGINT UNSIGNED NULL         -- referensi ke gfin.Vendor
supplier_name       VARCHAR(200) NULL
stock_receipt_id    BIGINT UNSIGNED NULL FK → gwin_t_stock_receipts  -- receipt asal
reason              TEXT NULL
status              ENUM('draft','confirmed','cancelled') DEFAULT 'draft'
confirmed_at        TIMESTAMP NULL
confirmed_by        BIGINT UNSIGNED NULL
notes               TEXT NULL
[standard cols]

-- gwin_t_stock_return_lines
id, uuid
stock_return_id     BIGINT UNSIGNED FK → gwin_t_stock_returns
item_id             BIGINT UNSIGNED FK → gwin_m_items
location_id         BIGINT UNSIGNED NULL FK
batch_id            BIGINT UNSIGNED NULL FK → gwin_m_stock_batches
qty_returned        DECIMAL(18,4) NOT NULL
uom_id              BIGINT UNSIGNED FK
unit_cost           DECIMAL(18,6) DEFAULT 0
total_cost          DECIMAL(18,2) DEFAULT 0
condition           ENUM('damaged','expired','wrong_item','other') DEFAULT 'damaged'
notes               TEXT NULL
[standard cols]
```

---

## Phase 5 — Stock Control

### Module: `StockOpname` (Stok Fisik / Stock Taking)

```sql
-- gwin_t_stock_opnames
id, uuid
opname_number       VARCHAR(50) UNIQUE NOT NULL
warehouse_id        BIGINT UNSIGNED FK → gwin_m_warehouses
opname_date         DATE NOT NULL
type                ENUM('full','partial','cycle_count')
status              ENUM('draft','in_progress','counting','reviewing','posted','cancelled') DEFAULT 'draft'
started_at          TIMESTAMP NULL
posted_at           TIMESTAMP NULL
posted_by           BIGINT UNSIGNED NULL
notes               TEXT NULL
[standard cols]

-- gwin_t_stock_opname_lines
id, uuid
stock_opname_id     BIGINT UNSIGNED FK → gwin_t_stock_opnames
item_id             BIGINT UNSIGNED FK → gwin_m_items
location_id         BIGINT UNSIGNED NULL FK
batch_id            BIGINT UNSIGNED NULL FK
qty_system          DECIMAL(18,4)           -- saldo sistem sebelum opname
qty_counted         DECIMAL(18,4) NULL      -- hasil hitung fisik
qty_difference      DECIMAL(18,4) GENERATED ALWAYS AS (qty_counted - qty_system)
unit_cost           DECIMAL(18,6) DEFAULT 0
difference_value    DECIMAL(18,2) GENERATED ALWAYS AS (qty_difference * unit_cost)
status              ENUM('pending','counted','verified') DEFAULT 'pending'
notes               TEXT NULL
[standard cols]
```

---

### Module: `StockAlert` (Minimum Stock & Expiry Alert)

```sql
-- gwin_cfg_stock_alerts  (threshold alert per item per gudang)
id, uuid
item_id             BIGINT UNSIGNED FK → gwin_m_items
warehouse_id        BIGINT UNSIGNED FK → gwin_m_warehouses
minimum_stock       DECIMAL(18,4) DEFAULT 0     -- alert jika stok < ini
reorder_point       DECIMAL(18,4) DEFAULT 0     -- auto-suggest reorder
maximum_stock       DECIMAL(18,4) NULL
expiry_alert_days   INT DEFAULT 30              -- alert N hari sebelum expired
is_active           BOOLEAN DEFAULT true
[standard cols]

-- gwin_h_stock_alerts_log  (history alert yang sudah terkirim)
id, uuid
item_id             BIGINT UNSIGNED FK
warehouse_id        BIGINT UNSIGNED FK
alert_type          ENUM('low_stock','expiry','overstock','no_movement')
current_value       DECIMAL(18,4)               -- stok aktual / hari expired
threshold_value     DECIMAL(18,4)               -- batas yang dilanggar
batch_id            BIGINT UNSIGNED NULL FK      -- untuk expiry alert
message             TEXT NULL
is_resolved         BOOLEAN DEFAULT false
resolved_at         TIMESTAMP NULL
created_at          TIMESTAMP
```

---

## Phase 6 — Chemical Safety (MSDS & B3)

### Module: `ChemicalSafety`

**Tujuan:** Kepatuhan keselamatan untuk bahan kimia berbahaya (sesuai GHS/SDS standar lab).

```sql
-- gwin_m_chemical_safety  (1:1 dengan gwin_m_items untuk item chemical)
id, uuid
item_id             BIGINT UNSIGNED FK → gwin_m_items UNIQUE
cas_number          VARCHAR(50) NULL             -- CAS Registry Number
chemical_formula    VARCHAR(200) NULL
hazard_class        VARCHAR(200) NULL            -- 'Flammable', 'Corrosive', dll
ghs_pictograms      JSON NULL                    -- ['GHS01','GHS02', ...] kode piktogram GHS
signal_word         ENUM('danger','warning') NULL
hazard_statements   TEXT NULL                    -- H-statements (H225, H301, dll)
precautionary_statements TEXT NULL               -- P-statements (P210, P260, dll)
storage_class       VARCHAR(50) NULL             -- VCI storage class
incompatible_with   TEXT NULL                    -- bahan yang tidak boleh disimpan bersama
disposal_method     TEXT NULL
first_aid_skin      TEXT NULL
first_aid_eye       TEXT NULL
first_aid_inhalation TEXT NULL
first_aid_ingestion TEXT NULL
fire_fighting       TEXT NULL
spill_procedure     TEXT NULL
ppe_required        JSON NULL                    -- ['gloves','goggles','lab_coat']
msds_version        VARCHAR(20) NULL
msds_date           DATE NULL
msds_file_path      VARCHAR(500) NULL            -- file PDF MSDS
is_b3               BOOLEAN DEFAULT false        -- Bahan Berbahaya & Beracun
b3_category         VARCHAR(100) NULL            -- kategori B3 sesuai regulasi Indonesia
[standard cols]

-- gwin_h_chemical_exposure_log  (log insiden/paparan)
id, uuid
item_id             BIGINT UNSIGNED FK → gwin_m_items
incident_date       TIMESTAMP NOT NULL
type                ENUM('spill','exposure','fire','other')
location            VARCHAR(200) NULL
involved_person     VARCHAR(200) NULL
description         TEXT
action_taken        TEXT
reported_by         BIGINT UNSIGNED
created_at          TIMESTAMP
```

---

## Phase 7 — Reporting

| Laporan | Basis Query | Module |
|---------|------------|--------|
| **Kartu Stok** | `gwin_h_stock_movements` per item | StockLedger |
| **Stok On-Hand per Gudang** | `gwin_m_stock_ledger` | StockLedger |
| **Nilai Persediaan** | `qty × average_cost` per item/gudang | StockLedger |
| **Barang Hampir Habis** | `qty < minimum_stock` | StockAlert |
| **Barang Hampir Expired** | `expiry_date <= today + N days` | StockBatch |
| **History Penerimaan** | `gwin_t_stock_receipts` | StockReceipt |
| **History Pengeluaran** | `gwin_t_stock_issues` | StockIssue |
| **Mutasi Gudang** | `gwin_h_stock_movements` per periode | StockLedger |
| **Rekap Opname** | `gwin_t_stock_opname_lines` | StockOpname |
| **Daftar Aset per Kategori** | `gwin_m_assets` | AssetRegister |
| **Jadwal Kalibrasi** | `gwin_m_assets.next_calibration_date` | AssetMaintenance |
| **Aset Dipinjam** | `gwin_t_asset_assignments` status=active | AssetAssignment |
| **Daftar Bahan B3** | `gwin_m_chemical_safety` is_b3=true | ChemicalSafety |
| **Konsumsi Bahan per Lab** | Issue linked ke `glims_work_order_id` | StockIssue |

---

## Integrasi Antar Aplikasi

```
gwin ↔ glims
    glims.WorkOrder created
        → gwin: cek ketersediaan bahan (query gwin_m_stock_ledger)
        → gwin: reservation qty (quantity_reserved++)
    glims.WorkOrder completed/cancelled
        → gwin: release reservation ATAU auto-post StockIssue

gwin ↔ gfin
    gwin_t_stock_receipts confirmed
        → event: StockReceiptConfirmed
        → gfin: trigger purchase invoice matching (3-way: PO → GR → Invoice)

    gwin_m_assets (acquisition_cost > threshold)
        → event: HighValueAssetRegistered
        → gfin: suggest create FixedAsset entry

    gwin_t_stock_opnames posted (selisih)
        → event: StockAdjustmentPosted
        → gfin: auto-create JournalEntry (Beban Penyesuaian Stok / Persediaan)

gwin ↔ gris (Procurement)
    gwin_cfg_stock_alerts triggered (low_stock)
        → event: LowStockAlertTriggered
        → gris: suggest create Purchase Request

gwin → employee (HR)
    gwin_t_asset_assignments overdue
        → notifikasi ke employee app atau email atasan
```

---

## Kode Aset — Format & Contoh

```
Format: {PREFIX}-{TAHUN}-{NOMOR_URUT_4_DIGIT}

Kategori          Prefix  Contoh
-----------------------------------------
Alat Lab          ALA     ALA-2026-0001
Alat Sampling     SAM     SAM-2026-0023
Glassware         GLS     GLS-2026-0010
PPE               PPE     PPE-2026-0005
Spare Part        SPR     SPR-2026-0002
Alat Ukur/Kalibr  CAL     CAL-2026-0008

Barcode/QR: encode uuid aset, bisa scan untuk:
- Cek detail aset
- Scan keluar/kembali (assignment)
- Input hasil maintenance
- Verifikasi opname
```

---

## Posting Rule Stok

| Transaksi | gwin_m_stock_ledger | gwin_h_stock_movements |
|-----------|-------------------|----------------------|
| Receipt confirmed | qty_on_hand + | INSERT receipt |
| Issue issued | qty_on_hand - | INSERT issue |
| Transfer out | qty_on_hand - (gudang asal) | INSERT transfer_out |
| Transfer in (received) | qty_on_hand + (gudang tujuan) | INSERT transfer_in |
| Opname posted (selisih +) | qty_on_hand + | INSERT adjustment_plus |
| Opname posted (selisih -) | qty_on_hand - | INSERT adjustment_minus |
| Return from lab | qty_on_hand + | INSERT return_in |

> **Aturan:** `gwin_h_stock_movements` TIDAK BOLEH diedit atau dihapus. Semua koreksi harus lewat transaksi baru.

---

## Urutan Development per Phase

```
1. Migration (tabel baru)
2. Domain/Models + Domain/Enums
3. Domain/Events
4. Domain/Contracts (RepositoryInterface)
5. Application/Actions (Commands: create, update, confirm, post)
6. Application/Queries
7. Domain/Services (AssetCodeGeneratorService, StockCalculatorService)
8. Infrastructure/Repositories
9. Infrastructure/Routes + Controllers
10. Resources/Pages (React + Inertia)
11. Tests (Feature test per use case)
```

---

## Prioritas Development gwin

```
Sprint 1: Phase 1 (Foundation master data)
Sprint 2: Phase 2 (Asset Register + Kode Aset) ← integrasi ke gfin Phase 5
Sprint 3: Phase 3 + 4 (Stock Ledger + Transaksi Gudang) ← integrasi ke gfin Phase 2
Sprint 4: Phase 5 (Stock Control: Opname + Alert)
Sprint 5: Phase 4 Tax ← gwin kirim event ke gfin
Sprint 6: Phase 6 (Chemical Safety — MSDS B3)
Sprint 7: Phase 7 (Reporting)
```
