Skip to content

Transactions & Posting

Almost every inventory movement — receipts, returns, transfers, disposals, adjustments — shares one generic posting mechanism: a draft header/detail pair is copied into a posted header/detail pair, discriminated by a document-type code, and then applied to stock. Learn this once and every transaction reads the same way.

The two table pairs

flowchart LR
    subgraph Draft["Draft / staging"]
        WH["tbTr_BoTrans_H"]
        WD["tbTr_BoTrans_D"]
    end
    subgraph Posted["Posted / committed"]
        PH["tbTr_BackOffice_H"]
        PD["tbTr_BackOffice_D"]
    end
    WH -->|"INSERT … SELECT *"| PH
    WD -->|"INSERT … SELECT *"| PD
    Draft -->|"DELETE after copy"| X["(draft cleared)"]
    PH --> STK["UpdateStock() via cSTOK"]
    style Draft fill:#fff3e0,stroke:#fb8c00
    style Posted fill:#e8f5e9,stroke:#388e3c
  • tbTr_BoTrans_H / _D — the draft header + detail, where a document is entered and edited on the input form.
  • tbTr_BackOffice_H / _D — the posted (permanent) header + detail.

Both pairs have identical schemas, which is why posting can copy with SELECT *. The header column BOH_TIPEDOC (a single character) says what kind of document it is.

The posting flow

Each transaction class (PenerimaanBarang, ReturBarang, …) exposes a Proses…() method that builds an ArrayList of SQL statements and runs them as one batch via HAKDAC.NonQuery:

sequenceDiagram
    autonumber
    participant Frm as Input form
    participant Cls as Transaction class
    participant Dac as HAKDAC
    participant Stk as cSTOK (STOCK)
    participant DB as SQL Server

    Frm->>Cls: Proses…(NoDoc, Tanggal, Cabang, Lokasi)
    Cls->>DB: INSERT tbTr_BackOffice_H SELECT * FROM tbTr_BoTrans_H WHERE NODOC=…
    Cls->>DB: UPDATE tbTr_BackOffice_H SET BOH_TGLDOC=…
    Cls->>DB: INSERT tbTr_BackOffice_D SELECT * FROM tbTr_BoTrans_D WHERE NODOC=…
    Cls->>DB: DELETE tbTr_BoTrans_H / _D (clear draft)
    Dac-->>Cls: success?
    Cls->>Cls: UpdateStock()
    loop each detail row
        Cls->>Stk: set movement buckets + GetAvgCost
        Stk->>DB: UPDATE tbMaster_Stock / tbMaster_Barang
    end

Steps:

  1. INSERT INTO tbTr_BackOffice_H SELECT * FROM tbTr_BoTrans_H WHERE BOH_NODOC='…'.
  2. UPDATE tbTr_BackOffice_H SET BOH_TGLDOC='…'.
  3. INSERT INTO tbTr_BackOffice_D SELECT * FROM tbTr_BoTrans_D WHERE BOD_NODOC='…'.
  4. DELETE the draft header + detail.
  5. If the batch succeeded → UpdateStock().

UpdateStock()

Re-reads the posted detail joined to the item master (tbTr_BackOffice_D LEFT JOIN tbMaster_Barang ON BOD_PRDCD = PRD_PRDCD) and, per line, moves inventory through the stock engine (cSTOK.UpdateSaldoAll). Bundle items (PRD_PAKET = 'Y') are exploded via tbMaster_BarangPaket so each component posts individually.

Table relationships

erDiagram
    tbTr_BoTrans_H    ||--|{ tbTr_BoTrans_D    : "draft header/detail"
    tbTr_BackOffice_H ||--|{ tbTr_BackOffice_D : "posted header/detail"
    tbTr_BoTrans_H    ||--o| tbTr_BackOffice_H : "posts to"
    tbMaster_Barang   ||--o{ tbTr_BackOffice_D : "item"
    tbMaster_Supplier ||--o{ tbTr_BackOffice_H : "supplier (receipts)"
    tbTr_BackOffice_H ||--o{ tbMaster_Stock    : "moves stock"
    tbMaster_NomorDoc ||--o{ tbTr_BackOffice_H : "numbers"
    tbTr_BackOffice_H {
        string BOH_KODECABANG PK
        string BOH_NODOC PK
        string BOH_TIPEDOC "doc type 1..9/M/S"
        string BOH_KODESUP FK
        string BOH_RECORDID "posted status"
        string BOH_NOREFF "source doc"
    }
    tbTr_BackOffice_D {
        string BOD_KODECABANG PK
        string BOD_NODOC PK
        int    BOD_NOURUT PK
        string BOD_PRDCD FK
        double BOD_QTY
        double BOD_HRGSAT
        string BOD_KODEPBR "P/R for repack"
    }
    tbMaster_Stock {
        string ST_KODECABANG PK
        string ST_LOKASI PK
        string ST_PRDCD PK
        double ST_QTYAKHIR "ending"
        double ST_AVGCOST
    }

Document types (BOH_TIPEDOC)

Code Indonesian Meaning Stock
1 Penerimaan Barang (BPB) Goods receipt from supplier/PO in
2 Retur Barang Return to supplier out
3 Mutasi Stock Internal transfer between locations move
4 Barang Hilang Lost / missing goods out
5 Mutasi / Konsinyasi Keluar Transfer / consignment out (packing list, DO) out
6 Mutasi / Konsinyasi Masuk Transfer / consignment in in
7 Stock Opname (& Ganti PLU) Physical count adjustment / item-code change ±
8 Pemusnahan Barang Disposal / write-off out
9 RePacking Repackaging (assemble / break packs) move
M MPP Inventory adjustment memo ±
S Pemakaian Sendiri Own / internal use out

5 and 6 are a pair

A transfer creates a 5 (out) at the origin and a 6 (in) at the destination. Consignment (frmInputKonsinyasi) reuses codes 1, 5, 6, 8, 9. Physical stock count (7) is entered through its own tbTr_StockOpname_* tables even though its effect is an adjustment.

What each transaction class does

Class Posts Stock effect Also updates
PenerimaanBarang (receipt) ProsesBPB + QTYBELI (qty + bonuses); recomputes avg cost tbMaster_Barang (PRD_HRGBELI/LASTCOST/AVGCOST/TGLBELI/KODESUP), tbMaster_Stock cost cols; proposed selling price, price history, approval rows; closes the source PO
ReturBarang (supplier return) ProsesReturBarang + QTYRBELI (out) avg recompute on cancel
RePackingBarang ProsesRePacking 'P' component → + QTYKELUAR; 'R' result → + QTYMASUK; recompute result avg PRD_AVGCOST of the 'R' item
MemoPenyesuaianPersediaan (MPP) ProsesMPP + QTYMASUK using header BOH_TIPESTOCK; recompute avg when qty > 0 PRD_AVGCOST
BarangHilang (lost / own-use) ProsesBarangHilang Out via QTYKELUAR; reversals negate and recompute avg PRD_AVGCOST on cancel
PrintNota CetakNota none — reprints Crystal doc by type

Reversals (Pembatalan…)

Every poster has a matching reversal: PembatalanBTB, PembatalanRetur, PembatalanRePacking, PembatalanMPP, PembatalanNBH, PembatalanPemusnahan, PembatalanPemakaianSendiri. A reversal re-reads the posted detail, applies the inverse movement (quantities × −1) through the stock engine, then DELETEs the header + detail from tbTr_BackOffice_H/_D. Status is tracked on BOH_RECORDID (posted flag) and references via BOH_NOREFF.

Document numbering

Two generators, both in MyLib/HAKFunction.vb (HAKFUNC):

Generator Mechanism When used
GetNomor(ConnStr, KdCab, KdDoc, NmDoc, NoAwal, Digit, Save, …) Counter table tbMaster_NomorDoc — reads NomorDoc, +1, zero-pads, optionally persists. Most documents. e.g. BP-<branch>-<yyyyMM>-00001.
NewNumber(ConnStr, Table, Field, Digit, Criteria, Awalan) SELECT MAX(field) + 1 — strips the numeric tail, increments, re-pads. Where no counter row exists; detail line numbers (MAX(NOURUT)).

MAX()+1 is a race condition

NewNumber (and inline MAX(NOURUT)+1) can hand the same number to two concurrent postings, producing duplicate document numbers under load. There is no counter lock on that path. See Known Issues.

Key columns for posting

Header (BOH_): NODOC, TGLDOC, TIPEDOC, KODELOK, TIPESTOCK/TIPESTOCK2, NOFAKTUR, NOREFF, KODESUP, KODEGUDANG, RECORDID. Detail (BOD_): PRDCD, QTY, HRGSAT, DISC1/DISC2, BONUS1/BONUS2, PPN, HRGGRAM, KODEPBR, NOURUT.