Skip to content

POS & Sales

The sales side of the product: ringing up a sale at the till, the shift/station model, tenders, cashier reset/settlement, and credit sales. Selling is where inventory leaves the system, so this ties back to Stock & Costing.

Scope note — the till UI lives elsewhere

This repository contains the POS data-access layer (SmartSoft.POS.FrameWork.DataAccess) and the back-office consumers of sales data (reset, reporting, delivery orders, credit sales). The customer-facing touch-screen ring-up terminal UI is a separate product, not in this checkout. What follows is the sales data model and the back-office flows that read/close it.

Table relationships

erDiagram
    tbMaster_Kasir    ||--o{ tbTr_Penjualan_H : "cashier rings up"
    tbMaster_Customer ||--o{ tbTr_Penjualan_H : "buyer"
    tbTr_Penjualan_H  ||--|{ tbTr_Penjualan_D : "header / detail"
    tbMaster_Barang   ||--o{ tbTr_Penjualan_D : "item sold"
    tbTr_Penjualan_H  ||--o{ tbTr_Piutang     : "credit sale"
    tbTr_Penjualan_R  ||--o{ tbTr_Penjualan_H : "recaps (per shift)"
    tbTr_Penjualan_S  ||--o{ tbTr_Penjualan_R : "rolls up (per day)"
    tbTr_Penjualan_H {
        string JLH_KODECABANG PK
        date   JLH_TGLTRN PK
        string JLH_STATION PK
        string JLH_SHIFT PK
        string JLH_NOSTRUK PK
        string JLH_USERID FK "cashier"
        string JLH_KODECUST FK
        double JLH_GROSS
        double JLH_CASH
        double JLH_PIUTANG
    }
    tbTr_Penjualan_D {
        string JLD_NOSTRUK PK
        int    JLD_NOURUT PK
        string JLD_PRDCD FK
        double JLD_QTY
        double JLD_HRGJUAL
        double JLD_AVGCOST
    }
    tbTr_Piutang {
        string PIU_NOSTRUK PK
        string PIU_KODECUST FK
        double PIU_NOMINAL
        double PIU_SALDO
    }

The sale document

A sale is a header + detail pair, tbTr_Penjualan_H / _D.

Station / shift model

The sales header key encodes the whole POS hierarchy:

flowchart LR
    B["Branch<br/>JLH_KODECABANG"] --> D["Date<br/>JLH_TGLTRN"]
    D --> S["Station<br/>JLH_STATION"]
    S --> U["Cashier<br/>JLH_USERID"]
    U --> SH["Shift<br/>JLH_SHIFT"]
    SH --> R["Receipt<br/>JLH_NOSTRUK"]
    style R fill:#e8f5e9,stroke:#388e3c

tbTr_Penjualan_H PK = JLH_KODECABANG + JLH_TGLTRN + JLH_STATION + JLH_USERID + JLH_SHIFT + JLH_TIPETRN + JLH_NOSTRUK. This is the widest key in the schema — it makes every receipt uniquely addressable by branch, day, till, cashier, and shift.

Tenders (how a sale is paid)

A single sale can split across many tender types — each is its own column on the header:

Column Tender
JLH_CASH Cash
JLH_DEBIT (+ JLH_NODEBIT, JLH_KODEBANKDEBIT) Debit card
JLH_CARD (+ JLH_NOCARD, JLH_KODEBANKKREDIT) Credit card
JLH_PIUTANG On account (credit sale → receivable)
JLH_VOUCHER Voucher
JLH_POINT (+ JLH_KODEPOINT, JLH_QTYPOINT) Loyalty points
JLH_TARIKTUNAI / JLH_CASHBACK Cash withdrawal / cash back
JLH_UANGMUKA / JLH_TITIPAN Down payment / deposit

Value & discount columns include JLH_QTY, JLH_HPP (COGS), JLH_GROSS, JLH_PPN, JLH_DISCOUNTITEM, JLH_DISCOUNTSTRUK, JLH_DISCOUNTVOUCHER, JLH_DISCOUNTCARD, JLH_DISCOUNTULTAH (birthday), JLH_PEMBULATAN (rounding).

Detail lines

tbTr_Penjualan_D PK = JLD_KODECABANG + JLD_NOSTRUK + JLD_NOURUT. Key columns: JLD_PRDCD (item/PLU), JLD_QTY, JLD_AVGCOST / JLD_LASTCOST (cost captured at sale for COGS), JLD_HRGJUAL, JLD_GROSS, JLD_PPN, JLD_DISCOUNT, JLD_QTYRETUR, JLD_PLUHADIAH (gift item from a promo), JLD_TRADEIN.

The in-progress basket lives in tbTemp_Penjualan / tbTemp_Penjualan_D_Member before the sale is committed.

Receipt printing

Receipts print through FrameWork.SmartSoft.Struk/StrukKasir.vb:

  • StrukKasir() / CreateStrukKasir() and the GDI path StrukKasirImage() (draws lines via PrintLine / DrawString, HeaderStruk(), CutStruk(), MaskCC() to mask card numbers).
  • Header/footer text and paper width come from PERUSAHAAN (PRSH_HEADSTRUK1..4, PRSH_FOOTER…, PRSH_LBRSTRUK, PRSH_LOGO, PRSH_PRINTERNAME) and parameters (PRM_STRUKDECIMAL, PRM_AUTOCUTTER, PRM_FONTSIZE).
  • The same file prints the opening-float slip (StrukKasAwalImage), goods pickup, and DO.

Cashiers are defined in tbMaster_Kasir (KSR_KODEKASIR, KSR_NAMAKASIR, KSR_STATION, KSR_STATUS, KSR_PASSWORD).

Cashier open → reset → close

The shift lifecycle is summarised into progressively higher aggregates by FrameWork.SmartSoft.BLL/ResetKasir.vb:

flowchart TD
    A["Sales rows<br/>tbTr_Penjualan_H/_D"] -->|"Reset() per station/shift"| B["Per-shift reset row<br/>tbTr_Penjualan_R (JLR_*)"]
    B -->|"CreateSummary() SUM by branch/date"| C["Daily store summary<br/>tbTr_Penjualan_S (JLS_*)"]
    D["ResetAll(): auto-close stale shifts<br/>(JLR_TGLTRN ≠ today, JLR_CLOSING ≠ Y)"] --> B
    style B fill:#fff3e0,stroke:#fb8c00
    style C fill:#e8f5e9,stroke:#388e3c
Method Does
ResetAll() Scans tbTr_Penjualan_R for any station/shift not closed today and resets each — auto-closing stale shifts.
Reset() Aggregates a station/shift/date's sales into the per-shift row tbTr_Penjualan_R (JLR_*): opening float JLR_KASAWAL, JLR_TOTALUANG, times JLR_JAMAWAL/JAMAKHIR, JLR_CLOSING, and sales-vs-return buckets (JLR_SJLH_* / JLR_RJLH_*).
CreateSummary() Rolls the per-shift rows into the daily store summary tbTr_Penjualan_S (JLS_*, incl. JLS_KASAWAL, JLS_SETTUNAI).

UI: Kasir/frmResetKasir.vb, plus frmSummarySales, frmDailyReport, frmCetakDataReset. Reset slips print via StrukReset.vb.

Settlement (EDC / card reconciliation)

Kasir/frmInputSettlement.vb writes tbTr_Penjualan_Settlement (STL_KODEEDC, STL_TIPE, STL_NOMINAL, per station/shift), joining tbTabel_EDC (card machines) × tbTabel_JenisBayar — the operator keys the settled amount per EDC × card type to reconcile against card tenders.

Sales orders & delivery

For non-immediate fulfilment, sales orders (Transaksi/SalesOrder/) capture an order, check/pick it, and cut a delivery order:

flowchart LR
    SO["Sales Order<br/>tbTr_SalesOrder_H/_D"] --> CHK["Checker<br/>tbTr_CheckerSO (CEK_*)"]
    CHK -->|"ValidasiSO(): QTYSO vs QTYREAL"| SO
    SO --> DO["Delivery Order<br/>frmCreateDeliveryOrder"]
    style SO fill:#e3f2fd,stroke:#1976d2
  • tbTr_SalesOrder_H (SOH_NOSO, SOH_NODO, SOH_KODECUST, SOH_QTYSO/SOH_QTYREAL, discounts, SOH_KODESALES) + detail tbTr_SalesOrder_D.
  • FrameWork.SmartSoft.BLL/SALESORDER.vb ValidasiSO() reconciles ordered vs checked quantities (tbTr_CheckerSO), writing back SOD_QTYREAL / SOH_QTYREAL and stamping CEK_STATUS.

Credit sales & receivables (Piutang)

A credit sale (tender JLH_PIUTANG) opens a receivable:

flowchart TD
    S["Credit sale (JLH_PIUTANG)"] --> P["tbTr_Piutang<br/>(PIU_NOMINAL, PIU_BAYAR, PIU_SALDO)"]
    P --> PAY["Payment: tbTr_BayarPiutang_H/_D<br/>(cash / giro / cheque)"]
    P --> CC["Credit control on tbMaster_Customer<br/>CUST_KREDITLIMIT − CUST_SALDOAKHIR"]
    style CC fill:#fff3e0,stroke:#fb8c00
  • Open A/RtbTr_Piutang per receipt (PIU_KODECUST, PIU_NOSTRUK, PIU_NOMINAL, PIU_BAYAR, PIU_SALDO).
  • PaymentfrmInputPembayaranPiutangtbTr_BayarPiutang_H (BPH_KODECUST, giro/cheque via BPH_KODEGIRO/BPH_NOMORGIRO, BPH_NOMINAL) + allocation detail tbTr_BayarPiutang_D (BPD_NOSTRUK, BPD_NOMINAL).
  • Credit control — on tbMaster_Customer: CUST_KREDITLIMIT, CUST_TOP (payment term), running CUST_PIUTANG/CUST_SALDOAKHIR; available credit = CUST_KREDITLIMIT − CUST_SALDOAKHIR.

Customer names are encrypted at rest

CUST_NAMACUST is stored with SQL Server DecryptByPassPhrase('5m@rt5oft', …) — a hard-coded passphrase in the SQL. Note it as a data-protection weakness alongside the items on Known Issues.

See Promotions & Loyalty for how discounts, gifts, vouchers, and points attach to a sale.