Build CoreOrdered learning track

Inventory, Warehouse, and Stock Ledger

Learn Java Large Scale ERP - Part 013

Deep dive into inventory, warehouse, and stock ledger design for large-scale Java ERP systems, including item identity, quantity semantics, stock movement lifecycle, reservation, allocation, bin/location control, lot and serial traceability, valuation boundary, concurrency, reconciliation, and failure modelling.

24 min read4727 words
PrevNext
Lesson 1334 lesson track0718 Build Core
#java#erp#inventory#warehouse+5 more

Part 013 — Inventory, Warehouse, and Stock Ledger

1. Target Skill Part Ini

Inventory dalam ERP besar bukan sekadar tabel item_stock dengan kolom quantity.

Inventory adalah sistem kebenaran operasional untuk menjawab pertanyaan yang sangat mahal jika salah:

  • barang apa yang dimiliki perusahaan;
  • berada di lokasi mana;
  • dalam status apa;
  • milik siapa;
  • tersedia untuk dijual atau tidak;
  • sudah dijanjikan ke order mana;
  • bisa dipindahkan, dipakai produksi, dikirim, dikarantina, atau harus diblokir;
  • bernilai berapa untuk accounting;
  • dapat ditelusuri ke lot, serial, supplier, receipt, shipment, atau production batch mana.

Skill inti part ini: mampu mendesain inventory, warehouse, dan stock ledger dalam Java ERP besar sehingga stock movement, reservation, allocation, warehouse execution, lot/serial traceability, valuation handoff, dan reconciliation tetap benar walaupun ada partial receipt, split shipment, transfer antar gudang, cycle count adjustment, duplicate command, race condition, return, quarantine, damaged goods, dan integration failure.

Inventory adalah domain yang sering terlihat sederhana sampai sistem masuk ke skala nyata.

Pada skala nyata, satu angka available_qty bisa dipengaruhi oleh:

  • sales order reservation;
  • pick list yang belum shipped;
  • goods receipt yang belum put-away;
  • transfer order yang masih in-transit;
  • stock opname yang sedang dihitung;
  • barang karantina quality control;
  • consignment stock;
  • returned goods yang belum inspected;
  • production issue;
  • WIP completion;
  • intercompany shipment;
  • negative stock policy;
  • unit-of-measure conversion;
  • lot expiry;
  • legal hold;
  • inventory valuation period.

Jika desain inventory salah, gejalanya muncul sebagai masalah bisnis yang sulit dipercaya:

  • sistem menunjukkan barang tersedia, tetapi warehouse tidak bisa menemukannya;
  • dua customer mendapatkan allocation atas unit yang sama;
  • stock ledger menunjukkan movement valid, tetapi GL inventory account tidak balance;
  • batch recall tidak bisa menemukan customer yang menerima lot tertentu;
  • cycle count adjustment dipakai untuk menutup bug transaksi;
  • shipment sukses tetapi stock tidak berkurang;
  • return masuk, tetapi available stock langsung naik padahal barang rusak;
  • MRP membeli terlalu banyak karena reserved/in-transit stock tidak dihitung benar;
  • report inventory by location berbeda dengan report stock valuation.

Part ini membangun model mental inventory sebagai ledger + execution + reservation + traceability + valuation boundary, bukan CRUD stock.


2. Kaufman Deconstruction: Memecah Skill Inventory ERP

Josh Kaufman menekankan bahwa skill kompleks harus dipecah menjadi sub-skill kecil yang bisa dilatih secara sadar. Untuk inventory ERP besar, sub-skill-nya adalah:

Sub-skillPertanyaan yang Harus Bisa DijawabOutput Engineering
Item identityApa yang dihitung sebagai barang yang sama?item, SKU, variant, packaging, UOM, barcode, GTIN mapping
Quantity semanticsQuantity apa yang sedang dibicarakan?on-hand, available, reserved, allocated, picked, in-transit, blocked, WIP
Location modelDi mana stock berada secara fisik/logis?site, warehouse, zone, aisle, rack, bin, virtual location
Stock movementPeristiwa apa yang mengubah stock?receipt, issue, transfer, adjustment, shipment, return, production movement
Stock ledgerBagaimana riwayat movement disimpan immutably?stock ledger entry, movement document, running/projection balance
ReservationBagaimana demand mengunci ketersediaan?soft reservation, hard allocation, expiry, priority
Warehouse executionBagaimana pekerjaan fisik dilakukan?receiving, put-away, replenishment, picking, packing, shipping, counting
TraceabilityBagaimana lot/serial dilacak?lot genealogy, serial assignment, recall path, expiry control
Valuation boundaryKapan movement memengaruhi nilai inventory?cost event, valuation layer, accounting handoff
ReconciliationBagaimana membuktikan stock benar?physical count, ledger balance, valuation balance, GL reconciliation
Concurrency controlBagaimana mencegah double allocation?lock strategy, versioning, idempotency, command key
Failure recoveryBagaimana membalik atau memperbaiki movement?reversal, correction, adjustment approval, exception queue

Latihan penting bukan menghafal istilah, tetapi menguji invariant.

Contoh practice cases:

  • goods receipt 100 unit, tetapi 8 unit rejected oleh quality control;
  • satu sales order butuh 10 unit, stock tersedia 6 unit, lalu datang receipt 5 unit;
  • dua order paralel berebut stock terakhir;
  • transfer dari Warehouse A ke Warehouse B hilang saat in-transit;
  • cycle count menemukan 97 unit padahal sistem 100 unit;
  • item dijual dalam box tetapi disimpan dalam each;
  • barang lot-controlled hampir expired dan tidak boleh dialokasikan;
  • return customer masuk tetapi belum boleh available sebelum inspection;
  • picking sudah terjadi tetapi shipment dibatalkan;
  • warehouse worker scan serial number yang salah;
  • duplicate event dari WMS mengirim shipment confirmation dua kali.

3. Inventory Sebagai Control Plane, Bukan Quantity Cache

Mental model yang paling aman:

Inventory Truth = Immutable Stock Ledger + Current Balance Projection + Reservation State + Warehouse Execution State + Traceability Evidence

current_balance boleh di-cache, tetapi kebenaran historis berasal dari ledger.

Ada beberapa lapisan kebenaran:

LayerPertanyaanMutable?Contoh
Movement documentApa transaksi bisnisnya?lifecycle-controlledreceipt document, transfer order, shipment document
Stock ledgerApa perubahan stock yang sah?append-only+100 received, -5 shipped, +3 returned
Balance projectionBerapa saldo sekarang?mutable projectionon-hand per item-location-lot
Reservation stateSiapa sudah mengklaim stock?mutable but controlledreserved for SO-1001
Warehouse taskPekerjaan fisik apa yang harus dilakukan?statefulpick task, put-away task
Traceability graphUnit/lot bergerak dari mana ke mana?append-only enoughlot received from vendor and shipped to customer
Valuation eventNilai finansial apa yang timbul?append-onlyinventory receipt cost event

Kesalahan umum adalah membuat stock_balance.quantity sebagai satu-satunya truth.

Dalam ERP serius, balance projection adalah materialized view atas ledger dan reservation state.


4. Quantity Semantics: Jangan Pernah Menggunakan Satu Kolom qty

Inventory rusak ketika tim tidak membedakan jenis quantity.

QuantityMaknaContoh Risiko
onHandQtyjumlah fisik/logis yang dimiliki di lokasimasih termasuk blocked/quarantine jika tidak dipisah
availableQtyjumlah yang boleh dialokasikan ke demand barusalah hitung menyebabkan oversell
reservedQtyjumlah diklaim oleh demand tetapi belum dipickdouble promise jika tidak atomik
allocatedQtyjumlah sudah dipilih spesifik dari stock poolconflict jika lot/bin berubah
pickedQtyjumlah sudah diambil dari bincancellation harus return-to-stock atau staging
packedQtyjumlah sudah dipackingshipment cancel lebih mahal
shippedQtyjumlah sudah keluar secara legal/fisikmemicu invoice/revenue boundary
inTransitQtyjumlah sedang transfer antar lokasitidak available di asal maupun tujuan
blockedQtyjumlah tidak boleh dipakaiquality/legal/hold
quarantineQtyjumlah menunggu inspectiontidak available untuk sales/production
damagedQtyjumlah rusakperlu write-off/claim
WIPQtymaterial berada dalam proses produksibukan finished goods

Rumus sederhana sering dipakai:

available = onHand - reserved - allocated - blocked - quarantine - damaged

Tetapi dalam ERP besar, rumus ini kurang lengkap karena availability bergantung pada context:

  • channel penjualan;
  • customer priority;
  • expiry rule;
  • warehouse eligibility;
  • lot attribute;
  • inventory ownership;
  • regulatory restriction;
  • ATP horizon;
  • project/contract allocation;
  • country/localization rule.

Maka lebih aman memakai konsep availability policy.

public record AvailabilityRequest(
        ItemId itemId,
        OrganizationScope scope,
        Quantity requestedQty,
        DemandType demandType,
        CustomerId customerId,
        LocalDate requiredDate,
        Set<LocationId> eligibleLocations,
        LotSelectionPolicy lotPolicy,
        ReservationPriority priority
) {}

public interface AvailabilityService {
    AvailabilityQuote quote(AvailabilityRequest request);
    ReservationResult reserve(ReserveInventoryCommand command);
}

Availability bukan query biasa. Availability adalah keputusan domain yang harus bisa diaudit.


5. Item Identity: Product, SKU, Variant, UOM, Lot, Serial

Sebelum menghitung stock, ERP harus tahu apa yang sedang dihitung.

5.1 Product vs SKU

Product sering merepresentasikan konsep komersial.

SKU merepresentasikan unit inventory yang benar-benar dihitung.

Contoh:

LevelContoh
ProductLaptop Model X
VariantLaptop Model X / 16GB / 512GB / Silver
SKULX-16-512-SLV-ID
Packaging SKUBox of 10 cable packs
Service itemInstallation service, tidak stock-controlled

Inventory ledger sebaiknya bergerak pada level yang cukup spesifik untuk menjamin correctness.

5.2 UOM Conversion

Unit of Measure adalah sumber bug besar.

Contoh:

  • beli dalam BOX;
  • simpan dalam EA;
  • jual dalam PACK;
  • produksi memakai KG;
  • accounting valuation memakai base UOM.

Prinsip:

  1. ledger menyimpan quantity dalam base UOM;
  2. document line menyimpan original UOM untuk audit;
  3. conversion snapshot disimpan saat transaksi;
  4. rounding policy eksplisit;
  5. perubahan conversion factor tidak boleh mengubah transaksi historis.
public record Quantity(
        BigDecimal value,
        UnitOfMeasure uom
) {
    public Quantity toBase(UomConversionSnapshot conversion) {
        return new Quantity(
                value.multiply(conversion.factor()).setScale(conversion.scale(), conversion.roundingMode()),
                conversion.baseUom()
        );
    }
}

5.3 Lot dan Serial

Lot-controlled item cocok untuk barang yang ditelusuri per batch:

  • makanan;
  • farmasi;
  • chemical;
  • spare part batch;
  • raw material production batch.

Serial-controlled item cocok untuk unit individual:

  • equipment;
  • electronics;
  • kendaraan;
  • regulated asset;
  • warranty-tracked item.

Lot dan serial bukan sekadar field tambahan. Keduanya mengubah lifecycle movement.

Jika item serial-controlled, shipment 10 unit harus tahu 10 serial number spesifik.

Jika item lot-controlled, allocation harus mengikuti policy seperti FIFO, FEFO, quality status, atau customer restriction.


6. Location Model: Site, Warehouse, Zone, Bin, Virtual Location

Inventory berada di lokasi fisik dan logis.

Model location harus mendukung:

  • receiving dock;
  • quality inspection;
  • put-away;
  • storage bin;
  • picking area;
  • packing station;
  • shipping dock;
  • in-transit;
  • vendor consignment;
  • customer-owned stock;
  • project stock;
  • quarantine;
  • scrap;
  • virtual adjustment location.

6.1 Location Type Mengontrol Movement

Location TypeBoleh Available?Contoh Movement
receivingtidak langsungreceipt -> inspection/put-away
quarantinetidakinspection pass/fail
storageya jika eligibleput-away, pick
pickingya untuk pick operationreplenishment, pick
stagingtidak untuk demand barupicked -> packed -> shipped
shipping docktidakship confirm
in-transittidaktransfer issue -> transfer receipt
scraptidakwrite-off
customer returntidak sebelum inspectionRMA receipt -> disposition

Jangan hanya menyimpan warehouse_id. Tanpa bin/status/location-type, sistem tidak tahu apakah stock benar-benar bisa dipakai.


7. Stock Movement Taxonomy

Semua perubahan inventory harus diklasifikasikan.

Movement TypeDirectionSource DocumentDampak
PURCHASE_RECEIPT+goods receipton-hand naik, cost event mungkin dibuat
SALES_SHIPMENT-shipment confirmationon-hand turun, COGS event mungkin dibuat
TRANSFER_ISSUE-transfer orderasal turun, in-transit naik
TRANSFER_RECEIPT+transfer orderin-transit turun, tujuan naik
PRODUCTION_ISSUE-work orderraw material ke WIP
PRODUCTION_RECEIPT+work orderfinished/semi-finished goods naik
ADJUSTMENT_IN+count adjustmentkoreksi dengan approval
ADJUSTMENT_OUT-count adjustment/write-offkoreksi dengan approval
RETURN_RECEIPT+RMAmasuk return/quarantine
SCRAP-scrap documentstock turun, expense/variance
RECLASSIFICATION+/-quality/status changeblocked -> available, lot status change

Movement type menentukan invariant.

Contoh:

  • SALES_SHIPMENT harus refer ke shipment line dan reservation/allocation;
  • PURCHASE_RECEIPT harus refer ke PO atau receiving document;
  • ADJUSTMENT_OUT harus butuh reason code dan approval;
  • PRODUCTION_RECEIPT harus refer ke work order;
  • RECLASSIFICATION tidak selalu mengubah total quantity, tetapi mengubah eligibility/status.

8. Stock Ledger Entry: Append-Only Evidence

Model ledger minimal:

Prinsip ledger:

  1. append-only;
  2. punya movement_doc_id dan source_line_id;
  3. menyimpan quantity_delta_base, bukan hanya after-balance;
  4. menyimpan original context untuk audit;
  5. bisa direversal dengan entry baru, bukan update/delete entry lama;
  6. bisa diproyeksikan ulang menjadi balance;
  7. bisa direkonsiliasi dengan document, warehouse task, dan accounting event.

8.1 Jangan Update Ledger Historis

Jika movement salah, buat correction/reversal.

Entry 1001: +100 PURCHASE_RECEIPT
Entry 1002: -100 REVERSAL_OF_1001
Entry 1003: +98 CORRECTED_PURCHASE_RECEIPT

Ini menjaga evidence.

Jika entry lama diedit:

  • audit trail pecah;
  • balance historis berubah diam-diam;
  • close period bisa berubah;
  • reconciliation dengan accounting gagal;
  • dispute sulit dibuktikan.

9. Stock Balance Projection

Stock balance adalah projection dari ledger.

Key balance biasanya bukan hanya (sku_id, warehouse_id).

Untuk ERP besar, key bisa berupa:

tenant_id,
legal_entity_id,
site_id,
warehouse_id,
location_id,
sku_id,
lot_id,
serial_id,
inventory_status,
ownership_type,
project_id,
contract_id,
valuation_bucket

Terlalu sederhana akan menghasilkan stock bercampur.

Terlalu detail akan menghasilkan query dan locking berat.

Trade-off-nya harus eksplisit.

9.1 Projection Update Pattern

@Transactional
public MovementResult postMovement(PostStockMovementCommand command) {
    IdempotencyKey key = command.idempotencyKey();

    if (movementRepository.existsByIdempotencyKey(key)) {
        return movementRepository.findResultByIdempotencyKey(key);
    }

    StockMovementDocument doc = movementFactory.create(command);
    movementValidator.validate(doc);

    List<StockLedgerEntry> entries = ledgerFactory.entriesFor(doc);

    for (StockLedgerEntry entry : entries) {
        StockBalance balance = balanceRepository.lockBalance(entry.balanceKey());
        balance.apply(entry);
        balance.assertNonNegativeIfPolicyRequires();
        ledgerRepository.append(entry);
        balanceRepository.save(balance);
    }

    outbox.publish(InventoryEvent.from(doc, entries));
    return MovementResult.posted(doc.id());
}

Catatan:

  • lockBalance tidak harus selalu pessimistic lock, tetapi stock-sensitive flows sering butuh atomicity kuat;
  • idempotency harus sebelum posting ledger;
  • validasi non-negative dilakukan pada balance key yang tepat;
  • outbox dipakai agar event tidak hilang setelah DB commit.

10. Reservation vs Allocation vs Picking

Banyak ERP gagal karena mencampur reservation, allocation, dan picking.

KonsepMaknaApakah Stock Spesifik?Contoh
Reservationdemand mengklaim availabilitybelum tentuSO-1001 reserve 10 unit SKU-A
Allocationsistem memilih stock pool spesifikya6 unit dari WH-A Lot L1, 4 unit dari WH-B Lot L2
Pickingoperator mengambil barang fisikya dan fisikscan bin/lot/serial
Packingbarang dikemasyacarton assignment
Shipmentbarang keluarya dan legalcarrier handoff, delivery doc

10.1 Soft Reservation

Soft reservation menjanjikan quantity tanpa memilih bin/lot spesifik.

Cocok untuk:

  • order awal;
  • availability promise;
  • demand planning;
  • short-lived carts.

Risiko:

  • race jika tidak dikunci;
  • janji terlalu optimistis;
  • sulit dioperasionalkan jika warehouse capacity terbatas.

10.2 Hard Allocation

Hard allocation memilih stock spesifik.

Cocok untuk:

  • wave picking;
  • lot/serial-controlled shipment;
  • priority order;
  • regulated product;
  • expiry-sensitive product.

Risiko:

  • allocation menjadi stale jika inventory dipindah manual;
  • cancellation harus mengembalikan stock;
  • warehouse task harus konsisten dengan allocation.

11. Available-to-Promise dan ATP Horizon

ATP menjawab: apakah bisa dipenuhi dan kapan?

Sumber ATP:

  • on-hand available;
  • scheduled receipt;
  • purchase order open;
  • production order planned/firmed;
  • transfer inbound;
  • reserved demand;
  • forecast consumption;
  • safety stock;
  • allocation rule.

ATP bukan hanya select available_qty.

ATP harus mempertimbangkan waktu.

Contoh:

Today: on-hand 6
Tomorrow: PO receipt +20
Demand today: SO-1 requires 5
Demand tomorrow: SO-2 requires 15
New order requires 10 in two days

ATP result: promise 10 in two days only if PO receipt trusted and SO-2 priority lower or sufficient remaining.

Untuk ERP besar, ATP output harus explainable:

{
  "requestedQty": 10,
  "promisedQty": 10,
  "promiseDate": "2026-07-02",
  "sources": [
    {"type": "ON_HAND", "qty": 1, "warehouse": "WH-A"},
    {"type": "PO_RECEIPT", "qty": 9, "documentNo": "PO-881", "expectedDate": "2026-07-01"}
  ],
  "constraints": ["SAFETY_STOCK_5_RESERVED", "FEFO_LOT_POLICY_APPLIED"]
}

Tanpa explainability, customer service dan planner tidak percaya sistem.


12. Warehouse Execution Model

Warehouse execution mengubah dokumen bisnis menjadi pekerjaan fisik.

Warehouse task minimal:

Status task:

Warehouse task tidak selalu langsung mengubah stock ledger.

Contoh:

  • pick task confirmation mungkin memindahkan stock dari storage ke staging;
  • ship confirmation mengurangi stock secara final;
  • put-away task memindahkan stock dari receiving ke storage;
  • cycle count task hanya menghasilkan evidence sampai adjustment disetujui.

13. Receiving Lifecycle

Receiving adalah titik masuk inventory dari procurement, return, transfer, atau production completion.

Inbound flow harus membedakan:

  • expected receipt;
  • actual receipt;
  • over-receipt;
  • under-receipt;
  • damaged receipt;
  • inspection pending;
  • accepted;
  • rejected;
  • returned to vendor;
  • put-away completed.

13.1 Receipt Invariant

Untuk PO receipt:

received_qty <= ordered_qty + allowed_over_receipt_tolerance
accepted_qty + rejected_qty + pending_inspection_qty = received_qty
posted_receipt_qty = sum(stock_ledger_entries for receipt)

Jika item lot/serial-controlled:

received_serial_count = accepted_serial_count + rejected_serial_count + pending_serial_count
no duplicate serial within same tenant/item scope
lot expiry must be valid according to item policy

14. Put-Away dan Replenishment

Put-away memindahkan stock dari receiving/staging ke storage.

Put-away rule mempertimbangkan:

  • item class;
  • hazardous class;
  • temperature requirement;
  • weight/volume;
  • bin capacity;
  • ABC class;
  • lot/expiry;
  • warehouse zone;
  • fast-moving vs slow-moving;
  • compatibility;
  • owner/project/customer restriction.
public interface PutAwayStrategy {
    List<PutAwaySuggestion> suggest(PutAwayContext context);
}

public record PutAwayContext(
        SkuId skuId,
        Quantity quantity,
        WarehouseId warehouseId,
        Optional<LotId> lotId,
        InventoryAttributes attributes,
        List<BinCapacitySnapshot> candidateBins
) {}

Replenishment memindahkan stock dari reserve storage ke pick face.

Jangan confuse replenishment dengan purchasing.

  • replenishment warehouse: memindahkan barang internal;
  • replenishment planning: membeli/produksi agar stock cukup.

15. Picking, Packing, Shipping

Outbound warehouse flow:

Outbound invariant:

picked_qty <= allocated_qty
packed_qty <= picked_qty
shipped_qty <= packed_qty
shipment_issue_qty = shipped_qty confirmed by shipping document

Untuk serial-controlled item:

each shipped unit must have exactly one serial number
serial number status moves AVAILABLE -> ALLOCATED -> PICKED -> SHIPPED
same serial cannot be shipped twice

Untuk lot-controlled item:

shipped lot must satisfy allocation policy
expired lot cannot be shipped if policy forbids
customer-restricted lot cannot be allocated to wrong customer/channel

16. Transfer Antar Warehouse dan In-Transit Stock

Transfer bukan update lokasi satu langkah.

Transfer besar memiliki lifecycle:

Ledger transfer sebaiknya eksplisit:

Source WH storage: -10 TRANSFER_ISSUE
In-transit location: +10 TRANSFER_IN_TRANSIT
In-transit location: -10 TRANSFER_RECEIPT_CLEAR
Destination receiving: +10 TRANSFER_RECEIPT
Destination storage: transfer/put-away internal movement

Jika hanya mengubah warehouse_id, sistem kehilangan evidence:

  • kapan barang keluar;
  • kapan barang diterima;
  • siapa carrier;
  • apakah ada selisih;
  • kapan risk/ownership berpindah;
  • bagaimana accounting intercompany dilakukan.

17. Cycle Count dan Stock Adjustment

Physical inventory tidak boleh diperlakukan sebagai update manual.

Cycle count lifecycle:

Count invariant:

variance_qty = counted_qty - system_qty_snapshot
adjustment_qty = approved_variance_qty
adjustment requires reason_code and approval if beyond tolerance
count snapshot must be captured before count starts

17.1 Snapshot Saat Count

Jika count dilakukan sambil transaksi berjalan, harus jelas policy-nya:

PolicyCara KerjaRisiko
freeze locationbin dibekukan sementaramengganggu operasi
snapshot-and-adjusthitung terhadap snapshotperlu reconcile movement selama counting
blind countworker tidak lihat system qtylebih objektif
recount thresholdvariance besar butuh recountlebih lambat tapi aman

ERP yang baik tidak memakai adjustment sebagai jalan pintas menyembunyikan bug.

Setiap adjustment besar harus menjadi signal investigasi.


18. Inventory Status dan Quality Control

Inventory status mengontrol eligibility.

Contoh status:

  • AVAILABLE;
  • RESERVED;
  • ALLOCATED;
  • PICKED;
  • STAGED;
  • IN_TRANSIT;
  • QUARANTINE;
  • QUALITY_HOLD;
  • DAMAGED;
  • EXPIRED;
  • SCRAP;
  • CUSTOMER_OWNED;
  • CONSIGNMENT;
  • LEGAL_HOLD.

Status transition harus dibatasi.

Jangan biarkan UI atau script bebas mengubah status tanpa movement document.

Status change adalah event domain.


19. Lot, Serial, dan Traceability Graph

Traceability menjawab:

  • item ini diterima dari supplier mana;
  • lot ini digunakan dalam production batch mana;
  • serial ini dikirim ke customer mana;
  • customer mana yang terdampak recall;
  • expiry lot mana yang masih tersimpan;
  • stock ini berada dalam chain of custody mana.

Traceability data minimal:

FieldKenapa Penting
source documentbukti asal
supplier/customerrecall dan dispute
lot/serial numberidentifikasi spesifik
movement timestampchain of custody
locationkeberadaan fisik
operator/systemaccountability
quality statuseligibility
parent-child relationproduction genealogy

Untuk regulated inventory, traceability bukan nice-to-have.


20. Valuation Boundary: Inventory Quantity vs Inventory Value

Inventory quantity dan inventory value berhubungan, tetapi jangan dicampur terlalu erat.

Movement yang berdampak kuantitas belum tentu langsung final secara nilai.

Contoh:

  • receipt terjadi sebelum invoice supplier;
  • landed cost datang belakangan;
  • transfer antar warehouse tidak mengubah total company inventory value tetapi bisa mengubah cost center/location;
  • production receipt butuh roll-up cost;
  • adjustment memicu variance;
  • return membutuhkan costing policy.

Prinsip desain:

  1. stock ledger menyimpan quantity truth;
  2. inventory subledger menyimpan value truth;
  3. cost event menghubungkan keduanya;
  4. rekonsiliasi memastikan quantity movement dan financial posting konsisten;
  5. period close mencegah perubahan historis tanpa adjustment period.

20.1 Costing Policy Boundary

Costing policy umum:

  • standard cost;
  • moving average;
  • FIFO layer;
  • specific identification;
  • weighted average periodic;
  • lot cost;
  • project cost.

Part ini tidak mengulang accounting engine dari Part 009/010, tetapi inventory harus menyediakan event yang cukup:

public record InventoryCostEvent(
        UUID eventId,
        UUID stockMovementDocumentId,
        UUID stockLedgerEntryId,
        SkuId skuId,
        LocationId locationId,
        Quantity quantity,
        MovementType movementType,
        CostingPolicy costingPolicy,
        Instant effectiveAt
) {}

21. Concurrency: Double Allocation dan Oversell

Inventory adalah domain concurrency-heavy.

Skenario klasik:

Stock available = 5
Order A reserve 5
Order B reserve 5
Keduanya membaca available = 5 sebelum update
Hasil: oversell 5 unit

21.1 Strategy 1: Pessimistic Lock Balance Row

@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("""
    select b from StockBalance b
    where b.balanceKey = :balanceKey
""")
Optional<StockBalance> lockForUpdate(BalanceKey balanceKey);

Cocok untuk:

  • stock terbatas;
  • high-value item;
  • strict non-negative stock;
  • reservation final.

Risiko:

  • contention;
  • deadlock jika multi-line order lock order tidak stabil;
  • throughput turun saat flash sale.

Mitigasi:

  • deterministic lock ordering;
  • lock partitioning by balance key;
  • short transaction;
  • command queue per hot SKU;
  • reservation bucket.

21.2 Strategy 2: Optimistic Version

update stock_balance
set reserved_qty = reserved_qty + :qty,
    available_qty = available_qty - :qty,
    version = version + 1
where balance_id = :id
  and available_qty >= :qty
  and version = :expected_version;

Cocok untuk:

  • moderate contention;
  • scalable reservation;
  • retry-friendly command.

Risiko:

  • retry storm;
  • starvation pada hot item;
  • error handling lebih kompleks.

21.3 Strategy 3: Reservation Ledger

Selain stock_balance, buat reservation sebagai ledger/state tersendiri.

available dihitung dari stock projection minus active reservation.

Cocok jika reservation sangat penting untuk audit dan lifecycle.


22. Idempotency untuk Movement dan WMS Event

Warehouse dan integrasi sering mengirim event duplicate.

Contoh:

  • scanner retry;
  • WMS timeout lalu mengirim ulang;
  • message broker redelivery;
  • mobile app offline sync;
  • user double-click confirm.

Setiap command perubahan stock harus punya idempotency key.

public record PostStockMovementCommand(
        String idempotencyKey,
        MovementType movementType,
        UUID sourceDocumentId,
        UUID sourceLineId,
        List<MovementLine> lines,
        Instant occurredAt,
        UserContext actor
) {}

Idempotency key harus berasal dari business identity, bukan random UUID setiap retry.

Contoh:

SHIP_CONFIRMATION:{shipmentId}:{shipmentLineId}:{confirmationAttemptNo}
WMS_PICK_CONFIRM:{wmsTaskId}:{scanSequence}
PO_RECEIPT:{receiptId}:{receiptLineId}
TRANSFER_RECEIPT:{transferId}:{destinationReceiptLineId}

Invariant:

same idempotency key => same logical result
same source document line cannot post same final movement twice

23. Reversal, Correction, dan Cancellation

Inventory tidak boleh menghapus movement historis.

SituasiSolusi
receipt salah quantity sebelum closereversal + corrected receipt
shipment salah lotreversal shipment + re-post correct lot jika secara operasional valid
transfer hilangloss adjustment / claim workflow
pick cancelledmove staging/picked stock back to eligible location
count salahrecount / correction adjustment
duplicate WMS eventidempotency rejects duplicate

Cancellation harus melihat lifecycle.

Order cancelled before allocation -> release reservation
Order cancelled after allocation -> deallocate stock
Order cancelled after pick -> return-to-stock movement
Order cancelled after shipment -> return/RMA process, not cancel shipment silently

24. Java Architecture Slice

Recommended package/capability structure:

inventory/
  application/
    command/
      ReserveInventoryHandler.java
      AllocateInventoryHandler.java
      PostStockMovementHandler.java
      ConfirmPickHandler.java
      ConfirmShipmentHandler.java
    query/
      AvailabilityQueryService.java
      StockBalanceQueryService.java
  domain/
    model/
      StockMovementDocument.java
      StockLedgerEntry.java
      StockBalance.java
      Reservation.java
      WarehouseTask.java
      Lot.java
      SerialNumber.java
    service/
      AvailabilityPolicy.java
      ReservationPolicy.java
      MovementValidator.java
      AllocationStrategy.java
      PutAwayStrategy.java
      TraceabilityService.java
    invariant/
      NonNegativeStockInvariant.java
      SerialUniquenessInvariant.java
      LotEligibilityInvariant.java
  infrastructure/
    persistence/
      StockBalanceRepository.java
      StockLedgerRepository.java
      ReservationRepository.java
    messaging/
      InventoryOutboxPublisher.java
    integration/
      WmsAdapter.java
      BarcodeAdapter.java

Command boundary:

public sealed interface InventoryCommand
        permits ReserveInventoryCommand,
                AllocateInventoryCommand,
                PostStockMovementCommand,
                ConfirmPickCommand,
                ConfirmShipmentCommand,
                AdjustInventoryCommand {
    String idempotencyKey();
    UserContext actor();
}

Domain service harus fokus pada invariant, bukan query UI.


25. Event Model untuk Integration

Inventory events:

EventTriggerConsumer
InventoryReservedreservation successOMS, ATP, analytics
InventoryReservationReleasedcancellation/expiryOMS, planning
InventoryAllocatedstock assignedWMS
StockMovementPostedledger entry appendedaccounting, BI, MRP
GoodsReceivedreceipt postedprocurement, quality, accounting
ShipmentConfirmedoutbound shipmentbilling, customer notification
InventoryAdjustedapproved adjustmentaccounting, audit
LotStatusChangedquality/release/holdsales, production, compliance

Payload harus menyertakan cukup identity untuk konsumen melakukan reconciliation.

{
  "eventId": "evt-001",
  "eventType": "StockMovementPosted",
  "movementDocumentId": "mov-100",
  "sourceDocumentType": "SHIPMENT",
  "sourceDocumentId": "sh-200",
  "skuId": "sku-1",
  "locationId": "wh-a-stage",
  "lotId": "lot-99",
  "quantityDeltaBase": "-5",
  "baseUom": "EA",
  "occurredAt": "2026-06-30T10:15:00Z"
}

Jangan hanya publish skuId dan qty.


26. Reporting dan Reconciliation

Inventory harus punya report operasional dan reconciliation report.

ReportTujuan
Stock on handsaldo per item/location/status
Available to promisequantity yang boleh dijanjikan
Aging inventorystock lama atau slow-moving
Expiry reportlot mendekati expiry
Negative stock reportviolation/policy exception
Reservation agingreservation terlalu lama
In-transit agingtransfer tertahan
Pick exceptiontask gagal/delay
Cycle count varianceselisih physical vs system
Stock ledger vs balanceprojection correctness
Inventory value reconciliationquantity vs financial value

Reconciliation formula dasar:

opening_balance + sum(ledger_delta in period) = closing_balance

Jika tidak cocok, cari:

  • ledger entry hilang;
  • projection update gagal;
  • manual balance update;
  • reversal salah;
  • period boundary salah;
  • timezone/effective date mismatch;
  • duplicate movement;
  • item/location key mismatch.

27. Observability: Business Metrics untuk Inventory

Technical metrics saja tidak cukup.

Business observability:

MetricSignal
negative stock countinvariant breach atau policy exception
reservation failure ratesupply shortage atau contention
allocation latencywarehouse/ATP bottleneck
pick exception ratedata/location quality buruk
in-transit agingtransfer atau carrier problem
cycle count variance rateinventory accuracy
duplicate command rejectedintegration retry health
ledger-balance mismatchcritical data corruption
expired available stockpolicy bug
quarantine agingquality bottleneck

Alert harus business-aware.

Contoh:

CRITICAL: stock ledger projection mismatch for SKU A / WH-1 / Lot L9.
Expected closing balance from ledger = 120, stock_balance = 118.
Last matching sequence = 888129, first mismatch sequence = 888130.

Ini jauh lebih berguna daripada InventoryService error rate high.


28. Performance Engineering untuk Inventory

Inventory workload berat:

  • high-frequency stock query;
  • reservation burst;
  • warehouse scanner events;
  • bulk receipt;
  • batch picking;
  • ATP calculation;
  • MRP netting;
  • stock valuation;
  • reporting by location/lot/status.

Strategi:

  1. pisahkan write model ledger dari read model availability;
  2. index balance key sesuai query dominan;
  3. gunakan partitioning ledger per tenant/time/item jika sangat besar;
  4. hindari aggregate query ledger untuk setiap availability check;
  5. cache reference data, bukan stock mutable sembarangan;
  6. short transaction untuk lock-sensitive updates;
  7. deterministic lock order untuk multi-line movement;
  8. asynchronous projection untuk report non-critical;
  9. synchronous projection untuk invariant stock critical;
  10. batch insert ledger entries untuk bulk operation.

Contoh index:

create unique index uq_stock_balance_key
on stock_balance (
  tenant_id,
  sku_id,
  location_id,
  coalesce(lot_id, '00000000-0000-0000-0000-000000000000'),
  inventory_status,
  ownership_type
);

create index idx_stock_ledger_sku_time
on stock_ledger_entry (tenant_id, sku_id, effective_at, sequence_no);

create index idx_reservation_demand
on inventory_reservation (tenant_id, demand_type, demand_id, status);

29. Testing Strategy

Inventory testing harus invariant-driven.

29.1 Unit Tests

  • UOM conversion rounding;
  • lot eligibility;
  • serial uniqueness;
  • non-negative stock policy;
  • reservation expiry;
  • allocation priority;
  • movement validation.

29.2 Integration Tests

  • post receipt updates ledger and balance;
  • duplicate receipt command returns same result;
  • shipment consumes allocation;
  • transfer creates in-transit stock;
  • cycle count adjustment requires approval;
  • outbox event emitted after commit.

29.3 Concurrency Tests

  • two reservations for last stock;
  • multi-line order deadlock prevention;
  • retry optimistic lock;
  • scanner duplicate pick confirmation;
  • same serial shipped twice attempt.

29.4 Property-Based Invariants

Generate random movements and assert:

balance = sum(ledger entries grouped by balance key)
no serial has two active physical locations
available never negative unless policy allows
reserved quantity cannot exceed reservable pool
shipment quantity cannot exceed allocated quantity

29.5 Golden Dataset

Buat dataset kecil:

  • 3 warehouses;
  • 10 SKUs;
  • 2 lot-controlled items;
  • 2 serial-controlled items;
  • 1 item with UOM conversion;
  • open purchase orders;
  • open sales orders;
  • transfers;
  • cycle count variance;
  • customer return;
  • quarantine stock.

Gunakan dataset ini untuk regression report dan reconciliation.


30. Common Anti-Patterns

Anti-patternGejalaDampak
Single qty columnavailable/on-hand/reserved bercampuroversell, wrong report
Mutable stock historytransaksi lama dieditaudit collapse
No idempotencyduplicate WMS eventstock double decrement
Warehouse as one locationbarang tidak bisa ditemukanexecution chaos
Reservation = allocationterlalu awal mengunci bin/lotwarehouse inefficiency
Manual adjustment as fix-allbug disembunyikanfinancial/reconciliation loss
No lot/serial lifecyclerecall impossiblecompliance failure
Reporting directly on OLTP ledgerreport lambatproduction performance drop
No valuation boundaryquantity dan accounting kacauGL mismatch
No period controlhistorical balance berubahclose process failure

31. Design Review Checklist

Gunakan checklist ini saat review inventory architecture.

31.1 Identity and Quantity

  • Apakah item, SKU, UOM, lot, serial, dan barcode dipisahkan dengan benar?
  • Apakah base UOM dan original UOM disimpan?
  • Apakah conversion snapshot disimpan pada transaksi?
  • Apakah quantity semantics eksplisit?

31.2 Ledger and Balance

  • Apakah stock ledger append-only?
  • Apakah reversal/correction memakai entry baru?
  • Apakah balance projection bisa direbuild dari ledger?
  • Apakah ledger entry punya source document identity?
  • Apakah balance key cukup detail tapi tidak over-fragmented?

31.3 Reservation and Warehouse

  • Apakah reservation, allocation, picking, packing, shipment dipisahkan?
  • Apakah reservation idempotent?
  • Apakah allocation obey lot/location/expiry policy?
  • Apakah warehouse task punya lifecycle?
  • Apakah cancellation berbeda berdasarkan lifecycle stage?

31.4 Control and Audit

  • Apakah adjustment butuh reason code dan approval?
  • Apakah quality hold/quarantine tidak bisa dilewati?
  • Apakah traceability lot/serial bisa menjawab recall?
  • Apakah period close mencegah perubahan historis?

31.5 Concurrency and Recovery

  • Apakah double allocation dicegah?
  • Apakah lock order deterministik?
  • Apakah duplicate scanner/WMS event aman?
  • Apakah failed movement masuk exception queue?
  • Apakah ledger-balance mismatch bisa dideteksi?

32. 20-Hour Practice Plan untuk Inventory ERP

Hour 1-3: Model Identity

  • Buat model item/SKU/UOM/lot/serial.
  • Tulis 10 contoh item dengan behavior berbeda.
  • Tentukan base UOM dan conversion snapshot.

Hour 4-6: Movement Ledger

  • Buat stock movement document dan ledger entry.
  • Implement receipt, shipment, transfer, adjustment.
  • Pastikan reversal append-only.

Hour 7-9: Balance Projection

  • Buat stock balance projection.
  • Tulis rebuild projection dari ledger.
  • Tulis reconciliation opening + movement = closing.

Hour 10-12: Reservation and Allocation

  • Implement reserve dan allocate.
  • Simulasikan dua order berebut stock terakhir.
  • Tambahkan idempotency.

Hour 13-15: Warehouse Task

  • Implement receiving, put-away, pick, ship confirmation.
  • Pisahkan pick cancellation dan shipment reversal.
  • Tambahkan serial scan validation.

Hour 16-18: Traceability and Quality

  • Buat lot genealogy.
  • Simulasikan quarantine -> available/rejected.
  • Buat recall query: lot -> shipments -> customers.

Hour 19-20: Failure Simulation

  • Duplicate WMS event.
  • Lost transfer.
  • Cycle count variance.
  • Ledger-balance mismatch.
  • Write rescue playbook.

33. Source Notes

Beberapa rujukan teknis dan domain yang relevan untuk part ini:

  • Jakarta Persistence mendefinisikan standar persistence dan object/relational mapping di lingkungan Java enterprise. Ini relevan untuk mapping ledger, balance, reservation, dan document lifecycle.
  • Jakarta EE Platform menyediakan layanan enterprise termasuk persistence, transactions, messaging, concurrency, dan batch yang sering muncul pada ERP besar.
  • PostgreSQL documentation menjelaskan transaction isolation dan explicit locking; konsep ini relevan untuk mencegah double allocation, oversell, dan race condition di stock-sensitive workflow.
  • GS1 General Specifications dan GS1 Global Traceability Standard relevan untuk identifier, barcode, dan traceability supply chain.
  • Apache OFBiz adalah ERP open-source berbasis Java yang mencakup accounting, order management, warehousing and inventory, manufacturing, dan MRP; ini berguna sebagai referensi domain ERP berbasis Java.

34. Ringkasan Mental Model

Inventory ERP yang benar tidak bertanya: “berapa quantity di tabel?”

Inventory ERP yang benar bertanya:

  1. quantity apa;
  2. untuk item/SKU/UOM/lot/serial mana;
  3. di lokasi fisik/logis mana;
  4. dalam status apa;
  5. milik siapa;
  6. tersedia untuk demand apa;
  7. diklaim oleh siapa;
  8. berasal dari movement mana;
  9. bisa dibuktikan melalui ledger apa;
  10. berdampak ke valuation/accounting bagaimana.

Model aman:

Movement Document -> Immutable Stock Ledger -> Balance Projection -> Reservation/Allocation -> Warehouse Execution -> Traceability -> Valuation Event -> Reconciliation

Jika hanya menguasai CRUD inventory, engineer bisa membuat aplikasi gudang sederhana.

Jika menguasai ledger, lifecycle, concurrency, traceability, warehouse execution, dan reconciliation, engineer mulai mampu mendesain ERP inventory yang tahan skala, audit, dan failure nyata.

Lesson Recap

You just completed lesson 13 in build core. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.