# Mesin Stok FIFO

Utilitas FIFO bersifat reusable dan dibagi menjadi dua lapis:

1. **Registry (`internal/utils/fifo`)** – mendeklarasikan tabel mana yang bersifat `Stockable` (sumber stok) atau `Usable` (pemakai stok). Setiap modul cukup menyebutkan nama tabel dan kolom wajib:
   - Stockable: `id`, `product_warehouse_id`, `total_qty`, `total_used_qty`, `created_at`
   - Usable: `id`, `product_warehouse_id`, `usage_qty`, `pending_qty`, `created_at`
2. **Service (`internal/common/service/common.fifo.service.go`)** – memakai registry tersebut untuk:
   - Menambah stok baru (`Replenish`).
   - Menyinkronkan total pemakaian (`Consume`). Method ini idempotent: panggil dengan *total kuantitas yang diinginkan* (mis. saat create/update/delete). Service menghitung selisih terhadap `usage_qty + pending_qty`, kemudian otomatis mengalokasikan tambahan atau melepaskan selisihnya.
   - Membatalkan pemakaian (`ReleaseUsage`) yang mengembalikan stok lalu memicu alokasi ulang ke antrian pending.
   - Baik `Replenish` maupun pelepasan stok akan menjalankan `resolvePendingForWarehouse`, sehingga pending tertua langsung terisi ketika stok tersedia.

## Registrasi tabel

```go
import (
    commonservice "gitlab.com/mbugroup/lti-api.git/internal/common/service"
    "gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
)

func init() {
    fifoSvc := commonservice.NewFifoService(db, stockAllocRepo, productWarehouseRepo, utils.Log)

    fifoSvc.RegisterStockable(fifo.StockableConfig{
        Key:   fifo.StockableKey("PURCHASE_DETAIL"),
        Table: "purchase_details",
        Columns: fifo.StockableColumns{
            ID:                 "id",
            ProductWarehouseID: "product_warehouse_id",
            TotalQuantity:      "total_qty",
            TotalUsedQuantity:  "total_used_qty",
            CreatedAt:          "created_at",
        },
    })

    fifoSvc.RegisterUsable(fifo.UsableConfig{
        Key:   fifo.UsableKey("RECORDING_STOCK"),
        Table: "recording_stocks",
        Columns: fifo.UsableColumns{
            ID:                 "id",
            ProductWarehouseID: "product_warehouse_id",
            UsageQuantity:      "usage_qty",
            PendingQuantity:    "pending_qty",
            CreatedAt:          "created_at",
        },
    })
}
```

Each registration optionally accepts an order clause or base scope (e.g. to exclude drafts).

Setiap registrasi bisa diberi klausa urutan atau scope dasar (mis. untuk mengecualikan draft).

## Menggunakan service di modul

1. **Saat stok masuk** (mis. purchase selesai): panggil `fifoSvc.Replenish(...)` dengan key stockable, id record, id product warehouse, dan kuantitas yang baru tersedia. Service akan:
   - Menambah `total_qty` pada tabel stockable,
   - Menambah `product_warehouses.quantity`,
   - Mencoba membersihkan `pending_qty` dari semua usable yang terdaftar (sesuai urutan FIFO).
2. **Saat modul memakai stok** (recording, marketing, dsb.) panggil `fifoSvc.Consume(...)` dengan total qty terbaru.
   - Jika qty baru lebih besar, service mengambil stok FIFO dan menambah `usage_qty`; kekurangan dicatat sebagai `pending_qty`.
   - Jika qty baru lebih kecil, service otomatis menurunkan `pending_qty` lebih dulu, lalu melepaskan alokasi aktif (stok kembali ke gudang) dan langsung dipakai untuk mengisi pending milik entitas lain.
   - Hapus data? panggil `Consume` dengan qty 0 atau gunakan `ReleaseUsage`.
3. **Jika dibatalkan penuh**: `fifoSvc.ReleaseUsage(...)` mengosongkan `usage_qty/pending_qty` dan menandai baris pivot sebagai `RELEASED`.

Tabel pivot (`stock_allocations`) menyimpan asal pemakaian secara presisi, sehingga audit trail dan rollback stok menjadi deterministik.
