# ?? Cross-App Master Data Plan
## Integrasi GLIMS · GFIN · GWIN · eCustomer · CRM

> **Status:** Draft — April 2026
> **Referensi:** `ARCHITECTURE_GUIDE.md`, `FINANCE_SCHEMA_PLANNING_GUIDE.md`, `WAREHOUSE_SCHEMA_PLANNING_GUIDE.md`, `COMPARISON_OLD_VS_NEW.md`
> **Pendekatan:** Single Database, Hard FK lintas app, Domain events untuk cross-module side effects.
> **Keputusan utama:** Domain app yang inisiasi record — GFIN menjadi backbone finansial.

---

## Daftar Isi

1. [Prinsip & Keputusan Arsitektur](#1-prinsip--keputusan-arsitektur)
2. [SO sebagai Buku Besar Lintas App](#2-so-sebagai-buku-besar-lintas-app)
3. [Ownership Master Data](#3-ownership-master-data)
4. [Tabel Global: `gfin_m_items`](#4-tabel-global-gfin_m_items)
5. [Relasi Domain Table ke Products](#5-relasi-domain-table-ke-products)
6. [Alur Sinkronisasi & Domain Events](#6-alur-sinkronisasi--domain-events)
7. [Schema SO Lintas App (GFIN)](#7-schema-so-lintas-app-gfin)
8. [Extension Tables per Item Type](#8-extension-tables-per-item-type)
9. [Daftar Lengkap Tabel Baru](#9-daftar-lengkap-tabel-baru)
10. [Menu & Halaman yang Dibangun](#10-menu--halaman-yang-dibangun)
11. [Permission Matrix](#11-permission-matrix)
12. [Aturan Bisnis & Rule Guide](#12-aturan-bisnis--rule-guide)
13. [Urutan Pengerjaan (Phase)](#13-urutan-pengerjaan-phase)
14. [Naming Convention Tabel](#14-naming-convention-tabel)
15. [Checklist Sebelum Implementasi](#15-checklist-sebelum-implementasi)

---

## 1. Prinsip & Keputusan Arsitektur

### 1.1 Fakta Dasar

| Aspek | Keputusan |
|---|---|
| **Database** | Single database — semua app share satu DB |
| **FK lintas app** | Hard FK (bukan soft UUID string reference) |
| **Komunikasi antar module** | Domain Events (`SampleTypeCreated`, dll) ? Listeners di module lain |
| **Harga produk** | Owner: CRM (future) — disimpan di `crm_m_price_lists`, bukan di product master |
| **Inisiasi `gfin_m_items`** | **Domain app yang inisiasi** (Opsi A): GLIMS/GWIN buat product + domain detail dalam satu transaksi |
| **GFIN review finansial** | Admin GFIN edit `revenue_account_id`, `tax_code`, `cogs_account_id` setelah product dibuat |

### 1.2 Filosofi

```
gfin_m_items  =  "KTP" setiap item/produk di seluruh platform
                     =  Satu baris = satu hal yang bisa dijual / dibeli / dipakai

Domain tables       =  "Detail profesi" masing-masing item:
  glims_m_sample_types     ? detail teknis lab (parameter, metode, TAT, akreditasi)
  gwin_m_items               ? detail fisik gudang (stok, barcode, shelf life)
  gwin_m_assets              ? detail aset tetap (depresiasi, kalibrasi, kondisi)
```

### 1.3 Yang Tidak Boleh Dilakukan

- ? `glims_m_sample_types` menyimpan harga atau akun CoA ? itu urusan GFIN
- ? `gwin_m_items` query langsung ke `glims_m_sample_types` ? lewat event/contract
- ? SO di GFIN menyimpan detail teknis lab ? cukup `item_id` FK
- ? Duplikasi data item di lebih dari satu tabel master

---

## 2. SO sebagai Buku Besar Lintas App

Sales Order (`gfin_t_sales_orders`) adalah **dokumen induk** yang menghubungkan semua app.

```
eCustomer (future)          CRM (future)             GLIMS
------------------          ------------             -----
Customer buat           ?   Sales set harga    ?    Lab terima SO
permintaan/quotation        & approve SO final       ? buat test orders
                                                      per so_item

                                                     GWIN
                                                     ----
                                                     Gudang keluarkan
                                                     consumable / alat
                                                     sampling per SO
                                                     ? gwin_t_stock_issues

                                                     GFIN
                                                     ----
                                                     SO selesai
                                                     ? auto-create invoice
                                                     ? posting jurnal otomatis
```

### Alur Detail SO

```
1. eCustomer / CRM buat gfin_t_sales_orders (status: draft)
2. Tiap baris SO = gfin_t_so_items (item_id ? gfin_m_items)
3. SO di-confirm:
   a. item_type = 'lab_test'
      ? event: SoItemConfirmed
      ? GLIMS listener: buat glims_t_test_orders (link ke so_item_id)
   b. item_type = 'consumable' / 'sampling_equipment'
      ? event: SoItemConfirmed
      ? GWIN listener: reserve stok / buat gwin_t_stock_reservations
4. GLIMS selesai pengujian ? event: TestOrderCompleted
   ? GFIN listener: update so_item.status = 'fulfilled'
5. Semua so_items fulfilled ? SO status = 'completed'
   ? GFIN auto-create gfin_t_invoices
   ? Auto-post ke gfin_t_journal_entries
```

---

## 3. Ownership Master Data

| Master Data | Owner App | Tabel | Catatan |
|---|---|---|---|
| **Item Catalog** | **GFIN** | `gfin_m_items` | Backbone seluruh platform |
| Lab Test Detail | GLIMS | `glims_m_sample_types` | Domain teknis lab |
| Item Fisik Detail | GWIN | `gwin_m_items` | Stok, barcode, storage |
| Aset Tetap Detail | GWIN | `gwin_m_assets` | Kalibrasi, depresiasi |
| Vendor / Supplier | GFIN | `gfin_m_vendors` | Finance yang bayar hutang |
| Customer | eCustomer | `ecustomer_m_customers` | Relasi bisnis awal |
| Chart of Accounts | GFIN | `gfin_m_chart_of_accounts` | Akuntansi |
| Unit of Measure | GWIN | `gwin_cfg_units_of_measure` | Satuan stok & jual |
| Currency & Rate | GFIN | `gfin_m_currencies` | Multi-currency |
| Price List | CRM (future) | `crm_m_price_lists` | Harga jual per customer/segmen |
| Employee | employee | `employee_m_employees` | HR domain |
| Regulasi Lab | GLIMS | `glims_m_regulations` | Regulasi uji lingkungan |
| Parameter & Metode | GLIMS | `glims_m_parameters`, `glims_m_methods` | Teknis LIMS |

---

## 4. Tabel Global: `gfin_m_items`

### 4.1 Schema Lengkap

```sql
-- Konvensi: gfin_m_items (Master, Owner: GFIN)
-- Dibuat bersamaan saat domain app inisiasi item baru

gfin_m_items
----------------------------------------------------------------
id                      BIGINT UNSIGNED AUTO_INCREMENT PK
uuid                    UUID UNIQUE

-- Identitas
code                    VARCHAR(50) UNIQUE NOT NULL    -- kode global lintas app (auto-generate)
name                    VARCHAR(300) NOT NULL           -- nama tampil di invoice/SO
item_type               ENUM(
                            'lab_test',                -- jasa pengujian ? inisiasi dari GLIMS
                            'consumable',              -- bahan habis pakai ? dari GWIN
                            'lab_equipment',           -- alat lab ? dari GWIN
                            'sampling_equipment',      -- alat sampling ? dari GWIN
                            'fixed_asset',             -- aset tetap ? dari GWIN gwin_m_assets
                            'training',                -- jasa pelatihan ? manual
                            'subcontract',             -- outsource pengujian ? manual
                            'other_service'            -- biaya lain-lain ? manual
                        ) NOT NULL
description             TEXT NULL
unit                    VARCHAR(20) NULL                -- PAKET, PCS, JAM, LITER, BOTOL

-- Flags operasional
is_sellable             BOOLEAN DEFAULT true            -- bisa masuk SO / invoice customer
is_purchasable          BOOLEAN DEFAULT false           -- bisa masuk PO ke vendor
is_inventoried          BOOLEAN DEFAULT false           -- perlu tracking stok di GWIN

-- Akun CoA (diisi/review oleh admin GFIN)
revenue_account_id      BIGINT UNSIGNED NULL FK ? gfin_m_chart_of_accounts
cogs_account_id         BIGINT UNSIGNED NULL FK ? gfin_m_chart_of_accounts
inventory_account_id    BIGINT UNSIGNED NULL FK ? gfin_m_chart_of_accounts  -- NULL jika non-stock
tax_code                VARCHAR(20) NULL                -- kode pajak default (PPN, PPh, dll)

-- Status
is_active               BOOLEAN DEFAULT true
deactivated_at          TIMESTAMP NULL
deactivated_by          BIGINT UNSIGNED NULL

-- Standard cols
created_by              BIGINT UNSIGNED NULL
updated_by              BIGINT UNSIGNED NULL
deleted_by              BIGINT UNSIGNED NULL
created_at              TIMESTAMP NULL
updated_at              TIMESTAMP NULL
deleted_at              TIMESTAMP NULL                  -- soft delete

INDEX(item_type)
INDEX(is_active)
INDEX(is_sellable)
INDEX(is_purchasable)
```

### 4.2 Contoh Data

| code | name | item_type | unit | is_sellable | is_purchasable | is_inventoried |
|---|---|---|---|---|---|---|
| `ITM-LAB-00001` | BOD5 - Air Sungai | lab_test | PAKET | ? | ? | ? |
| `ITM-LAB-00002` | pH - Air Limbah | lab_test | PAKET | ? | ? | ? |
| `ITM-CON-00001` | Kertas Filter Whatman 42 | consumable | LEMBAR | ? | ? | ? |
| `ITM-CON-00002` | Asam Sulfat 98% | consumable | LITER | ? | ? | ? |
| `ITM-EQP-00001` | pH Meter Hanna HI9813 | lab_equipment | UNIT | ? | ? | ? |
| `ITM-AST-00001` | Spektrofotometer UV-Vis | fixed_asset | UNIT | ? | ? | ? |
| `ITM-TRN-00001` | Pelatihan Sampling Air | training | SESI | ? | ? | ? |
| `ITM-SVC-00001` | Biaya Pengiriman Sampel | other_service | TRIP | ? | ? | ? |

### 4.3 Auto-Generate Code

Format code: `ITM-{TYPE_PREFIX}-{SEQ:5}`

| item_type | prefix |
|---|---|
| lab_test | LAB |
| consumable | CON |
| lab_equipment | EQP |
| sampling_equipment | SAM |
| fixed_asset | AST |
| training | TRN |
| subcontract | SUB |
| other_service | SVC |

---

## 5. Relasi Domain Table ke Products

### 5.1 GLIMS: `glims_m_sample_types` ? `gfin_m_items`

```sql
-- Tambahkan kolom ke glims_m_sample_types
item_id      BIGINT UNSIGNED NOT NULL ? gfin_m_items.id

-- Saat admin GLIMS buat sample type baru:
-- 1. Buat record gfin_m_items (item_type='lab_test', is_sellable=true)
-- 2. Buat record glims_m_sample_types (item_id = id dari step 1)
-- Keduanya dalam satu DB transaction
```

### 5.2 GWIN: `gwin_m_items` ? `gfin_m_items`

```sql
-- Tambahkan kolom ke gwin_m_items
item_id      BIGINT UNSIGNED NOT NULL ? gfin_m_items.id

-- Saat admin GWIN buat item baru:
-- 1. Buat record gfin_m_items (item_type sesuai kategori item)
-- 2. Buat record gwin_m_items (item_id = id dari step 1)
```

### 5.3 GWIN: `gwin_m_assets` ? `gfin_m_items`

```sql
-- Tambahkan kolom ke gwin_m_assets
item_id      BIGINT UNSIGNED NOT NULL ? gfin_m_items.id

-- Saat admin GWIN registrasi aset baru:
-- 1. Buat record gfin_m_items (item_type='fixed_asset')
-- 2. Buat record gwin_m_assets (item_id = id dari step 1)
-- 3. Buat record gfin_m_fixed_assets (dari event AssetRegistered, untuk depresiasi)
```

---

## 6. Alur Sinkronisasi & Domain Events

### 6.1 Event: SampleTypeCreated (GLIMS ? GFIN)

```
Trigger:    Admin GLIMS submit form "Tambah Jenis Sampel"
Action:     CreateSampleTypeAction::execute()
            +-- DB::transaction() {
            ¦       1. create gfin_m_items (item_type='lab_test')
            ¦       2. create glims_m_sample_types (item_id = ^.id)
            ¦   }
            +-- fire: SampleTypeCreated($sampleType)

Listener (GFIN):  NotifyFinanceTeamOfNewProduct
            ? kirim notifikasi ke tim GFIN untuk melengkapi CoA mapping
```

### 6.2 Event: WarehouseItemCreated (GWIN ? GFIN)

```
Trigger:    Admin GWIN submit form "Tambah Item"
Action:     CreateWarehouseItemAction::execute()
            +-- DB::transaction() {
            ¦       1. create gfin_m_items (item_type dari category)
            ¦       2. create gwin_m_items (item_id = ^.id)
            ¦   }
            +-- fire: WarehouseItemCreated($item)

Listener (GFIN):  SetupDefaultAccountMapping
            ? auto-map CoA berdasarkan item_type (bisa dikonfigurasi di GFIN settings)
```

### 6.3 Event: AssetRegistered (GWIN ? GFIN)

```
Trigger:    Admin GWIN registrasi aset baru
Action:     RegisterAssetAction::execute()
            +-- DB::transaction() {
            ¦       1. create gfin_m_items (item_type='fixed_asset')
            ¦       2. create gwin_m_assets (item_id = ^.id)
            ¦   }
            +-- fire: AssetRegistered($asset)

Listener (GFIN):  CreateFixedAssetRecord
            ? create gfin_m_fixed_assets
            ? setup jadwal depresiasi awal
```

### 6.4 Event: SoItemConfirmed (GFIN ? GLIMS/GWIN)

```
Trigger:    SO di-confirm oleh sales/manager
Event:      SoItemConfirmed($soItem)

Listener (GLIMS):  CreateTestOrderFromSoItem
            ? IF so_item.product.item_type == 'lab_test'
            ? create glims_t_test_orders (so_item_id, status='queued')

Listener (GWIN):   ReserveStockFromSoItem
            ? IF so_item.product.is_inventoried == true
            ? create gwin_t_stock_reservations (so_item_id, qty)
```

### 6.5 Event: TestOrderCompleted (GLIMS ? GFIN)

```
Trigger:    Analis approve LHU / hasil uji final
Event:      TestOrderCompleted($testOrder)

Listener (GFIN):  MarkSoItemFulfilled
            ? update gfin_t_so_items.status = 'fulfilled'
            ? IF semua so_items fulfilled ? update SO status = 'completed'
            ? IF SO completed ? auto-create gfin_t_invoices
```

---

## 7. Schema SO Lintas App (GFIN)

### 7.1 `gfin_t_sales_orders`

```sql
gfin_t_sales_orders
------------------------------------------------
id                      BIGINT UNSIGNED PK
uuid                    UUID UNIQUE

so_number               VARCHAR(50) UNIQUE NOT NULL     -- auto-generate: SO/2026/04/00001
customer_id             BIGINT UNSIGNED FK ? ecustomer_m_customers

-- Tanggal
order_date              DATE NOT NULL
delivery_date           DATE NULL                       -- target selesai
confirmed_at            TIMESTAMP NULL
completed_at            TIMESTAMP NULL

-- Status workflow
status                  ENUM(
                            'draft',
                            'confirmed',        -- SO dikonfirmasi, trigger domain events
                            'in_progress',      -- ada test order / reservasi stok aktif
                            'completed',        -- semua item fulfilled
                            'invoiced',         -- sudah dibuatkan invoice
                            'cancelled'
                        ) DEFAULT 'draft'

-- Referensi dokumen
quotation_number        VARCHAR(50) NULL                -- dari CRM (future)
customer_po_number      VARCHAR(100) NULL               -- nomor PO customer
crm_opportunity_id      BIGINT UNSIGNED NULL            -- FK ke CRM (future)

-- Nilai (di-compute dari so_items)
subtotal                DECIMAL(18,2) DEFAULT 0
discount_amount         DECIMAL(18,2) DEFAULT 0
tax_amount              DECIMAL(18,2) DEFAULT 0
total_amount            DECIMAL(18,2) DEFAULT 0
currency_id             BIGINT UNSIGNED FK ? gfin_m_currencies
exchange_rate           DECIMAL(18,6) DEFAULT 1

notes                   TEXT NULL
internal_notes          TEXT NULL                       -- tidak tampil ke customer

-- Standard cols
created_by              BIGINT UNSIGNED NULL
updated_by              BIGINT UNSIGNED NULL
deleted_by              BIGINT UNSIGNED NULL
created_at, updated_at, deleted_at

INDEX(status), INDEX(order_date), INDEX(customer_id)
```

### 7.2 `gfin_t_so_items`

```sql
gfin_t_so_items
------------------------------------------------
id                      BIGINT UNSIGNED PK
uuid                    UUID UNIQUE

so_id                   BIGINT UNSIGNED FK ? gfin_t_sales_orders
item_id              BIGINT UNSIGNED FK ? gfin_m_items  -- SATU referensi global

-- Quantity & harga (dari CRM price list saat SO dibuat)
qty                     DECIMAL(18,4) NOT NULL
unit                    VARCHAR(20) NOT NULL
unit_price              DECIMAL(18,2) NOT NULL
discount_pct            DECIMAL(5,2) DEFAULT 0
discount_amount         DECIMAL(18,2) DEFAULT 0
subtotal                DECIMAL(18,2) NOT NULL           -- qty * unit_price - discount

-- Status fulfillment
status                  ENUM(
                            'pending',          -- belum diproses
                            'in_progress',      -- test order / reservasi aktif
                            'fulfilled',        -- selesai (test done / barang keluar)
                            'cancelled'
                        ) DEFAULT 'pending'

-- Link ke domain (diisi otomatis oleh listener)
glims_test_order_id     BIGINT UNSIGNED NULL            -- untuk lab_test items
gwin_stock_issue_id     BIGINT UNSIGNED NULL            -- untuk inventoried items

notes                   TEXT NULL
sort_order              TINYINT DEFAULT 0

-- Standard cols
created_by              BIGINT UNSIGNED NULL
updated_by              BIGINT UNSIGNED NULL
created_at, updated_at

INDEX(so_id), INDEX(item_id), INDEX(status)
```

---

## 8. Extension Tables per Item Type

### 8.1 `gfin_m_fixed_assets` (Extension: item_type = 'fixed_asset')

```sql
gfin_m_fixed_assets
------------------------------------------------
id                      BIGINT UNSIGNED PK
uuid                    UUID UNIQUE

item_id              BIGINT UNSIGNED NOT NULL UNIQUE ? gfin_m_items
wh_asset_id             BIGINT UNSIGNED NOT NULL UNIQUE ? gwin_m_assets     -- dari GWIN

-- Identitas aset
asset_category          ENUM(
                            'vehicle',              -- kendaraan
                            'lab_equipment',        -- alat lab
                            'office_equipment',     -- peralatan kantor
                            'building',             -- bangunan/gedung
                            'land',                 -- tanah
                            'furniture',            -- furnitur
                            'other'
                        )

-- Perolehan
acquisition_date        DATE NOT NULL
acquisition_cost        DECIMAL(18,2) NOT NULL       -- harga perolehan
salvage_value           DECIMAL(18,2) DEFAULT 0      -- nilai sisa akhir masa manfaat

-- Depresiasi
depreciation_method     ENUM(
                            'straight_line',         -- garis lurus
                            'declining_balance',     -- saldo menurun
                            'none'                   -- tanah, tidak didepresiasi
                        ) DEFAULT 'straight_line'
useful_life_months      INT NOT NULL                 -- masa manfaat (bulan)
depreciation_rate       DECIMAL(8,6) NULL            -- untuk declining balance (%)

-- Nilai buku (di-update tiap periode)
accumulated_depreciation DECIMAL(18,2) DEFAULT 0     -- akumulasi depresiasi
net_book_value          DECIMAL(18,2) NULL            -- harga perolehan - akumulasi
last_depreciated_at     DATE NULL                    -- tanggal depresiasi terakhir

-- Akun CoA (bisa berbeda per kategori aset)
asset_account_id        BIGINT UNSIGNED FK ? gfin_m_chart_of_accounts    -- akun aset
depreciation_account_id BIGINT UNSIGNED FK ? gfin_m_chart_of_accounts    -- akun beban depresiasi
accumulated_account_id  BIGINT UNSIGNED FK ? gfin_m_chart_of_accounts    -- akun akum. depresiasi

-- Standard cols
created_by, updated_by, deleted_by
created_at, updated_at, deleted_at
```

### 8.2 `gfin_t_asset_depreciations` (History Depresiasi per Periode)

```sql
gfin_t_asset_depreciations
------------------------------------------------
id                      BIGINT UNSIGNED PK
uuid                    UUID UNIQUE

fixed_asset_id          BIGINT UNSIGNED FK ? gfin_m_fixed_assets
accounting_period_id    BIGINT UNSIGNED FK ? gfin_cfg_accounting_periods

period_year             SMALLINT NOT NULL
period_month            TINYINT NOT NULL
depreciation_amount     DECIMAL(18,2) NOT NULL
accumulated_after       DECIMAL(18,2) NOT NULL   -- akumulasi setelah periode ini
net_book_value_after    DECIMAL(18,2) NOT NULL   -- NBV setelah periode ini

-- Link ke jurnal
journal_entry_id        BIGINT UNSIGNED NULL FK ? gfin_t_journal_entries

is_posted               BOOLEAN DEFAULT false
posted_at               TIMESTAMP NULL
posted_by               BIGINT UNSIGNED NULL
notes                   TEXT NULL

created_by              BIGINT UNSIGNED NULL
created_at              TIMESTAMP

UNIQUE(fixed_asset_id, period_year, period_month)
```

---

## 9. Daftar Lengkap Tabel Baru

### 9.1 Tabel Baru di GFIN

| # | Tabel | Tipe | Keterangan |
|---|---|---|---|
| 1 | `gfin_m_items` | Master | **Global item catalog — backbone platform** |
| 2 | `gfin_t_sales_orders` | Transaction | SO induk lintas app |
| 3 | `gfin_t_so_items` | Transaction | Line items SO |
| 4 | `gfin_m_fixed_assets` | Master | Extension aset tetap (depresiasi) |
| 5 | `gfin_t_asset_depreciations` | Transaction | History depresiasi per periode |

### 9.2 Kolom Baru di Tabel Existing

| Tabel | Kolom Baru | Tipe | Keterangan |
|---|---|---|---|
| `glims_m_sample_types` | `item_id` | BIGINT FK | ? `gfin_m_items` |
| `gwin_m_items` | `item_id` | BIGINT FK | ? `gfin_m_items` |
| `gwin_m_assets` | `item_id` | BIGINT FK | ? `gfin_m_items` |
| `gwin_m_assets` | `fin_asset_id` | BIGINT FK NULL | ? `gfin_m_fixed_assets` |

### 9.3 Tabel GLIMS (dari COMPARISON_OLD_VS_NEW.md — tidak berubah)

| # | Tabel | Tipe | Status |
|---|---|---|---|
| 1 | `glims_m_sample_types` | Master | + tambah `item_id` |
| 2 | `glims_m_sample_type_categories` | Master | Pertahankan |
| 3 | `glims_m_sample_type_fields` | Master | Pertahankan |
| 4 | `glims_m_regulations` | Master | Gabungkan |
| 5 | `glims_m_regulation_appendices` | Master | Tambahkan |
| 6 | `glims_r_regulation_attachments` | Reference | Pertahankan |
| 7 | `glims_m_parameters` | Master | Gabungkan |
| 8 | `glims_m_parameter_categories` | Master | Pertahankan |
| 9 | `glims_m_parameter_accreditations` | Master | Pertahankan |
| 10 | `glims_m_parameter_aliases` | Master | Pertahankan |
| 11 | `glims_m_methods` | Master | Gabungkan |
| 12 | `glims_p_method_parameters` | Pivot | Gabungkan |
| 13 | `glims_p_sample_type_parameters` | Pivot | Pertahankan |
| 14 | `glims_m_sample_type_groups` | Master | Tambahkan |
| 15 | `glims_p_group_sample_types` | Pivot | Tambahkan |
| 16 | `glims_m_quality_standards` | Master | Tambahkan |
| 17 | `glims_m_quality_standard_params` | Master | Tambahkan |
| 18 | `glims_t_test_orders` | Transaction | **Baru — link ke SO** |

### 9.4 Tabel GWIN (dari WAREHOUSE_SCHEMA_PLANNING_GUIDE.md — tidak berubah)

| # | Tabel | Tipe | Status |
|---|---|---|---|
| 1 | `gwin_m_items` | Master | + tambah `item_id` |
| 2 | `gwin_m_warehouses` | Master | Sudah ada |
| 3 | `gwin_m_warehouse_locations` | Master | Sudah ada |
| 4 | `gwin_m_assets` | Master | + tambah `item_id`, `fin_asset_id` |
| 5 | `gwin_cfg_units_of_measure` | Config | Sudah ada |
| 6 | `gwin_cfg_item_categories` | Config | Sudah ada |
| 7 | `gwin_t_stock_receipts` | Transaction | Sudah ada |
| 8 | `gwin_t_stock_issues` | Transaction | Sudah ada |
| 9 | `gwin_t_stock_reservations` | Transaction | **Baru — dari SO confirm** |
| 10 | `gwin_h_stock_movements` | History | Sudah ada |

---

## 10. Menu & Halaman yang Dibangun

### 10.1 GFIN — Module: Item Catalog

```
Sidebar: Master Data ? Katalog Item

Menu:
+-- Katalog Item (Index)        GET  /items
¦   +-- Filter: item_type, is_sellable, is_purchasable, is_inventoried, is_active
¦   +-- Kolom: code, name, item_type badge, unit, is_sellable, is_purchasable, CoA status
¦   +-- Actions: Edit CoA Mapping, Deactivate
¦
+-- Detail Item (Show)          GET  /items/{uuid}
¦   +-- Tab: Info Umum
¦   ¦   +-- code, name, item_type, unit, flags
¦   +-- Tab: Akun & Pajak
¦   ¦   +-- revenue_account, cogs_account, inventory_account, tax_code
¦   +-- Tab: Domain Detail
¦       +-- Link ke GLIMS/GWIN dengan info spesifik domain
¦
+-- Edit CoA Mapping (Edit)       GET  /items/{uuid}/edit-finance
    +-- Form: revenue_account_id, cogs_account_id, inventory_account_id, tax_code
```

> Catatan: GFIN tidak punya form Create produk secara langsung.
> Produk dibuat dari GLIMS (lab test) atau GWIN (item/aset).
> GFIN hanya review & lengkapi mapping finansial.

### 10.2 GFIN — Module: Sales Order

```
Sidebar: Penjualan ? Sales Order

Menu:
+-- Daftar SO (Index)             GET  /sales-orders
¦   +-- Filter: status, order_date range, customer
¦   +-- Kolom: so_number, customer, order_date, total, status badge, progress bar
¦
+-- Buat SO (Create)              GET  /sales-orders/create
¦   +-- Header: customer, order_date, currency, quotation_number, customer_po_number
¦   +-- Line Items: item_id (search), qty, unit, unit_price, discount, subtotal
¦
+-- Detail SO (Show)              GET  /sales-orders/{uuid}
¦   +-- Header info
¦   +-- Tab: Line Items — status tiap item (pending/in_progress/fulfilled)
¦   +-- Tab: Test Orders (jika ada lab_test items) — link ke GLIMS
¦   +-- Tab: Stok Keluar (jika ada inventoried items) — link ke GWIN
¦   +-- Tab: Invoice yang dibuat dari SO ini
¦
+-- Konfirmasi SO                 POST /sales-orders/{uuid}/confirm
¦   +-- Trigger domain events ke GLIMS dan GWIN
¦
+-- Buat Invoice dari SO         POST /sales-orders/{uuid}/create-invoice
    +-- Hanya bisa jika status = 'completed'
```

### 10.3 GFIN — Module: Fixed Assets

```
Sidebar: Aset ? Aset Tetap

Menu:
+-- Daftar Aset (Index)           GET  /fixed-assets
¦   +-- Filter: asset_category, depreciation_method, status
¦   +-- Kolom: kode aset, nama, kategori, harga perolehan, NBV, akumulasi depresiasi
¦
+-- Detail Aset (Show)            GET  /fixed-assets/{uuid}
¦   +-- Info perolehan, masa manfaat, metode depresiasi
¦   +-- Tab: Jadwal Depresiasi (tabel per bulan: depresiasi, akumulasi, NBV)
¦   +-- Tab: History Posting (jurnal yang sudah diposting)
¦   +-- Tab: Info Fisik (link ke GWIN gwin_m_assets: kondisi, lokasi, kalibrasi)
¦
+-- Jalankan Depresiasi Bulanan   POST /fixed-assets/run-depreciation
¦   +-- Batch generate gfin_t_asset_depreciations untuk periode aktif
¦
+-- Posting Jurnal Depresiasi     POST /fixed-assets/post-depreciation/{period}
    +-- Batch post ke gfin_t_journal_entries
```

### 10.4 GLIMS — Form Tambah Jenis Sampel (Diupdate)

```
Existing form: Tambah Jenis Sampel
Perubahan: Tambah section "Produk & Penjualan"

Form sections:
+-- Informasi Teknis Lab (existing)
¦   +-- code, name, category, description
¦   +-- storage_requirement, handling_instruction, handling_time
¦   +-- has_dynamic_fields, lhu_format_id
¦
+-- [BARU] Produk & Penjualan
    +-- item_code        ? auto-generate (ITM-LAB-XXXXX), bisa di-override
    +-- item_name        ? default sama dengan sample type name, bisa diubah
    +-- unit                ? PAKET / SAMPEL / dll (dropdown)
    +-- Info: "Mapping akun CoA akan dilengkapi oleh tim Finance"
```

### 10.5 GWIN — Form Tambah Item (Diupdate)

```
Existing form: Tambah Item
Perubahan: Tambah section "Produk & Keuangan"

Form sections:
+-- Informasi Item (existing)
¦   +-- code, name, item_category, brand, model_number
¦   +-- unit_of_measure, barcode, minimum_stock, reorder_point
¦   +-- shelf_life_days, storage_condition
¦
+-- [BARU] Produk & Keuangan
    +-- item_code        ? auto-generate, bisa di-override
    +-- item_name        ? default dari item name
    +-- is_purchasable      ? toggle (default true untuk GWIN items)
    +-- is_inventoried      ? toggle (default true untuk non-asset)
    +-- Info: "Mapping akun CoA akan dilengkapi oleh tim Finance"
```

### 10.6 GWIN — Form Registrasi Aset (Diupdate)

```
Existing form: Registrasi Aset
Perubahan: Tambah section "Aset Tetap & Depresiasi"

Form sections:
+-- Informasi Fisik (existing)
¦   +-- item_id, serial_number, brand, model
¦   +-- acquisition_date, condition, warehouse_id
¦   +-- next_calibration_date, calibration_interval_days
¦
+-- [BARU] Aset Tetap & Depresiasi
    +-- asset_category          ? dropdown (Kendaraan/Alat Lab/dll)
    +-- acquisition_cost        ? harga perolehan (Rp)
    +-- salvage_value           ? nilai sisa (Rp)
    +-- depreciation_method     ? dropdown (Garis Lurus/Saldo Menurun/Tidak)
    +-- useful_life_months      ? masa manfaat (bulan)
```

---

## 11. Permission Matrix

### 11.1 Format Permission

Sesuai `ARCHITECTURE_GUIDE.md` format: `{module}.{action}`

### 11.2 Permissions Baru (Item Catalog)

```
finance.item.view               -- lihat daftar & Detail Item
finance.item.edit_finance       -- edit CoA mapping, tax_code
finance.item.deactivate         -- nonaktifkan produk

Catatan: finance.item.create tidak ada — produk dibuat dari domain app masing-masing
```

### 11.3 Permissions SO

```
finance.so.view                    -- lihat SO
finance.so.create                  -- buat SO baru
finance.so.edit                    -- edit SO yang masih draft
finance.so.confirm                 -- konfirmasi SO (trigger domain events)
finance.so.cancel                  -- batalkan SO
finance.so.create_invoice          -- buat invoice dari SO completed
```

### 11.4 Permissions Fixed Assets

```
finance.fixed_asset.view                   -- lihat daftar aset
finance.fixed_asset.run_depreciation       -- jalankan depresiasi bulanan
finance.fixed_asset.post_depreciation      -- posting jurnal depresiasi
finance.fixed_asset.edit                   -- edit info finansial aset
```

### 11.5 Role Matrix

| Permission | Finance Admin | Finance Staff | Accounting | Auditor | Lab Admin | Warehouse Admin |
|---|---|---|---|---|---|---|
| `finance.item.view` | ? | ? | ? | ? | ? | ? |
| `finance.item.edit_finance` | ? | ? | ? | ? | ? | ? |
| `finance.so.create` | ? | ? | ? | ? | ? | ? |
| `finance.so.confirm` | ? | ? | ? | ? | ? | ? |
| `finance.fixed_asset.run_depreciation` | ? | ? | ? | ? | ? | ? |
| `finance.fixed_asset.post_depreciation` | ? | ? | ? | ? | ? | ? |

---

## 12. Aturan Bisnis & Rule Guide

### 12.1 Aturan: gfin_m_items

```
R-ITM-01: item_id di glims_m_sample_types / gwin_m_items / gwin_m_assets TIDAK BOLEH NULL.
          Satu domain record = satu product record. Dibuat bersamaan dalam satu transaction.

R-ITM-02: Satu gfin_m_items hanya boleh direferensi oleh SATU domain record.
          Tidak boleh satu item_id di-share ke dua sample_type.

R-ITM-03: Jika gfin_m_items di-deactivate, domain record juga harus di-deactivate
          secara bersamaan.

R-ITM-04: item_code bersifat immutable setelah dibuat (boleh di-override saat create,
          tidak boleh diubah setelahnya).

R-ITM-05: Hanya GFIN admin (permission: finance.item.edit_finance) yang boleh
          mengubah revenue_account_id, cogs_account_id, tax_code.

R-ITM-06: Kolom is_inventoried=true hanya valid untuk item_type:
          consumable, lab_equipment, sampling_equipment.
          lab_test, training, subcontract, other_service ? is_inventoried harus false.
```

### 12.2 Aturan: Sales Order

```
R-SO-01:  SO hanya bisa di-confirm jika memiliki minimal 1 so_item.

R-SO-02:  Setelah SO di-confirm, line items TIDAK BISA ditambah/dihapus.
          Perubahan hanya bisa lewat SO Amendment (future feature).

R-SO-03:  SO yang sudah 'completed' atau 'invoiced' TIDAK BISA di-cancel
          tanpa approval dari Finance Admin.

R-SO-04:  Invoice hanya bisa dibuat jika SO.status = 'completed'
          (semua so_items.status = 'fulfilled').

R-SO-05:  so_item.unit_price diambil dari CRM price list saat SO dibuat
          dan TIDAK berubah meskipun harga berubah di masa depan (snapshot price).

R-SO-06:  so_item.glims_test_order_id diisi otomatis oleh GLIMS listener
          (bukan diisi manual oleh user GFIN).

R-SO-07:  so_item.gwin_stock_issue_id diisi otomatis oleh GWIN listener.
```

### 12.3 Aturan: Depresiasi Aset

```
R-DEP-01: Satu aset, satu baris depresiasi per periode (UNIQUE constraint).
          Tidak bisa double-posting satu periode.

R-DEP-02: Depresiasi hanya bisa dijalankan untuk accounting_period.status = 'open'.

R-DEP-03: Aset dengan depreciation_method = 'none' (tanah) tidak di-generate
          entri depresiasi.

R-DEP-04: Akumulasi depresiasi tidak boleh melebihi (acquisition_cost - salvage_value).
          Setelah penuh, aset dianggap fully depreciated dan tidak lagi di-generate.

R-DEP-05: Jurnal depresiasi harus:
          DEBIT  ? depreciation_expense_account (beban depresiasi)
          CREDIT ? accumulated_depreciation_account (akumulasi depresiasi)

R-DEP-06: Posting jurnal depresiasi bersifat immutable.
          Koreksi hanya bisa lewat jurnal reversal.
```

### 12.4 Aturan: Domain Events

```
R-EVT-01: Semua domain events harus dijalankan SETELAH DB transaction berhasil commit.
          Gunakan: dispatch($event)->afterCommit()

R-EVT-02: Listener antar app TIDAK boleh gagal diam-diam.
          Jika listener GLIMS gagal membuat test_order, harus:
          a. Log error ke activity log
          b. Kirim notifikasi ke admin GLIMS
          c. SO item tetap di status 'pending' (bukan 'in_progress')

R-EVT-03: Listener harus idempotent — aman jika dipanggil lebih dari sekali
          (pakai upsert / check sebelum insert).
```

### 12.5 Aturan: Cross-App FK

```
R-FK-01:  item_id di semua domain tables menggunakan ON DELETE RESTRICT.
          gfin_m_items tidak bisa dihapus jika masih ada referensi domain.

R-FK-02:  Untuk soft delete: jika gfin_m_items.deleted_at terisi,
          semua domain records yang mereferensi harus di-soft-delete juga.
          Gunakan observer / event untuk cascade soft delete.

R-FK-03:  Hard delete (force delete) dilarang untuk tabel yang ada di cross-app references.
          Cukup soft delete.
```

---

## 13. Urutan Pengerjaan (Phase)

### Phase 0 — Prerequisite (Sebelum Semua Phase Lain)

- [ ] Buat migration `gfin_m_items` (tabel kosong)
- [ ] Buat migration `gfin_t_sales_orders` + `gfin_t_so_items`
- [ ] Buat `ItemType` Enum di GFIN
- [ ] Tambah kolom `item_id` ke `glims_m_sample_types` (nullable dulu ? isi data ? NOT NULL)
- [ ] Tambah kolom `item_id` ke `gwin_m_items` (idem)
- [ ] Tambah kolom `item_id` ke `gwin_m_assets` (idem)
- [ ] Seeder: generate `gfin_m_items` dari existing data GLIMS & GWIN

### Phase 1 — GFIN: Item Catalog UI

- [ ] Module: `ItemCatalog`
- [ ] Model: `Item` (table: `gfin_m_items`)
- [ ] Enum: `ItemType`
- [ ] Query: `GetItemsQuery` (filter, search, paginate)
- [ ] Action: `UpdateItemFinanceAction` (edit CoA mapping)
- [ ] Action: `DeactivateItemAction`
- [ ] Controller: `ItemController` (index, show, edit-finance)
- [ ] Policy: `ItemPolicy`
- [ ] Pages: `Item/Index.tsx`, `Item/Show.tsx`, `Item/EditFinance.tsx`

### Phase 2 — GLIMS & GWIN: Form Update + Events

- [ ] GLIMS: Update `CreateSampleTypeAction` ? wrap dalam transaction + create product
- [ ] GLIMS: Update form `SampleType/Create.tsx` ? tambah section "Produk & Penjualan"
- [ ] GWIN: Update `CreateWarehouseItemAction` ? wrap dalam transaction + create product
- [ ] GWIN: Update form `Item/Create.tsx` ? tambah section "Produk & Keuangan"
- [ ] GWIN: Update `RegisterAssetAction` ? wrap dalam transaction + create product
- [ ] GWIN: Update form `Asset/Register.tsx` ? tambah section "Aset Tetap & Depresiasi"
- [ ] Buat domain events: `SampleTypeCreated`, `WarehouseItemCreated`, `AssetRegistered`
- [ ] Buat listeners: `NotifyFinanceTeamOfNewProduct`

### Phase 3 — GFIN: Sales Order

- [ ] Module: `SalesOrder`
- [ ] Models: `SalesOrder`, `SoItem`
- [ ] Enums: `SalesOrderStatus`, `SoItemStatus`
- [ ] Events: `SoItemConfirmed`, `SalesOrderCompleted`
- [ ] Action: `CreateSalesOrderAction`
- [ ] Action: `ConfirmSalesOrderAction` ? dispatch events
- [ ] Action: `CancelSalesOrderAction`
- [ ] Query: `GetSalesOrdersQuery`
- [ ] Controller: `SalesOrderController`
- [ ] Pages: `SalesOrder/Index.tsx`, `SalesOrder/Create.tsx`, `SalesOrder/Show.tsx`

### Phase 4 — GLIMS: Test Order dari SO

- [ ] Buat tabel `glims_t_test_orders`
- [ ] Listener: `CreateTestOrderFromSoItem` (handle `SoItemConfirmed`)
- [ ] Event: `TestOrderCompleted`
- [ ] GFIN Listener: `MarkSoItemFulfilled` (handle `TestOrderCompleted`)

### Phase 5 — GWIN: Stock Reservation dari SO

- [ ] Buat tabel `gwin_t_stock_reservations`
- [ ] Listener: `ReserveStockFromSoItem` (handle `SoItemConfirmed`)
- [ ] Event: `StockIssued`
- [ ] GFIN Listener: `MarkSoItemFulfilled` (handle `StockIssued`)

### Phase 6 — GFIN: Fixed Asset & Depreciation

- [ ] Migration: `gfin_m_fixed_assets`, `gfin_t_asset_depreciations`
- [ ] Model: `FixedAsset`, `AssetDepreciation`
- [ ] Listener: `CreateFixedAssetRecord` (handle `AssetRegistered` dari GWIN)
- [ ] Domain Service: `DepreciationCalculatorService`
  - `calculateStraightLine(FixedAsset $asset, int $month): decimal`
  - `calculateDecliningBalance(FixedAsset $asset, int $month): decimal`
- [ ] Action: `RunMonthlyDepreciationAction` (batch generate per periode)
- [ ] Action: `PostDepreciationJournalAction` (post ke jurnal)
- [ ] Controller: `FixedAssetController`
- [ ] Pages: `FixedAsset/Index.tsx`, `FixedAsset/Show.tsx`

### Phase 7 — GFIN: Auto-Invoice dari SO

- [ ] Action: `CreateInvoiceFromSalesOrderAction`
- [ ] Trigger: saat `SalesOrderCompleted` event fire
- [ ] Map so_items ? invoice_items (snapshot harga dari SO)

---

## 14. Naming Convention Tabel

Sesuai `ARCHITECTURE_GUIDE.md` §19:

```
Format: {app}_{type}_{nama_plural}

GFIN tables:
  gfin_m_items                 ? Master
  gfin_m_fixed_assets             ? Master
  gfin_t_sales_orders             ? Transaction
  gfin_t_so_items                 ? Transaction
  gfin_t_asset_depreciations      ? Transaction

GLIMS tables (existing + baru):
  glims_m_sample_types               ? Master (+ item_id)
  glims_t_test_orders                ? Transaction (baru)

GWIN tables (existing + baru):
  gwin_m_items                         ? Master (+ item_id)
  gwin_m_assets                        ? Master (+ item_id, gfin_asset_id)
  gwin_t_stock_reservations            ? Transaction (baru)
```

> ?? Perhatikan: GWIN menggunakan prefix `wh_` bukan `gwin_`
> (sudah ditetapkan di `WAREHOUSE_SCHEMA_PLANNING_GUIDE.md` dan tidak diubah).

---

## 15. Checklist Sebelum Implementasi

### Database & Migration

- [ ] Semua tabel baru mengikuti konvensi `{app}_{type}_{nama_plural}`
- [ ] Semua tabel punya kolom standard sesuai tipe (`m_` / `t_` / `r_` / `h_` / `p_`)
- [ ] Setiap model set `protected $table` secara eksplisit (tidak pakai auto-prefix)
- [ ] FK cross-app menggunakan `ON DELETE RESTRICT`
- [ ] UUID ada di semua tabel Master dan Transaction
- [ ] Index dibuat untuk kolom yang sering difilter (`status`, `item_type`, `is_active`)

### Domain Events

- [ ] Event dispatch menggunakan `->afterCommit()` (tidak fire sebelum transaction commit)
- [ ] Semua listener bersifat idempotent
- [ ] Listener yang gagal dikirim ke dead-letter queue / logged ke activity log

### Business Rules

- [ ] `gfin_m_items` dibuat bersamaan dengan domain record (satu transaction)
- [ ] item_code immutable setelah save
- [ ] SO snapshot harga dari CRM saat dibuat (tidak follow harga saat ini)
- [ ] Depresiasi UNIQUE per (fixed_asset_id, year, month) — no double posting
- [ ] Depresiasi tidak melebihi (acquisition_cost - salvage_value)

### Security

- [ ] Policy `ItemPolicy` terpasang — hanya GFIN yang bisa edit CoA mapping
- [ ] Endpoint confirm SO dilindungi permission `finance.so.confirm`
- [ ] Endpoint run depreciation dilindungi permission `finance.fixed_asset.run_depreciation`
- [ ] UUID dipakai di URL (bukan integer ID)

### Frontend

- [ ] Semua form baru menggunakan komponen shared dari `packages/ui-shadcn`
- [ ] Style enterprise compact (cell `py-1.5`, tombol `h-8`/`h-9`)
- [ ] Dark mode compatible (pakai token warna, tidak hardcode)
- [ ] Semua status menggunakan `Badge` dengan warna dari Enum `color()`
