# Modul Penjualan, Retur, dan Pembatalan — Sintesis Teknis ERP `san`

> **Cakupan dokumen ini.** Sintesis dari 12 dokumen sumber mencakup: cetak biru arsitektur modul `penjualan`, `penjualanproject`, `pembatalan`, dan `saham`; standar kualitas ISO/IEC 25010 untuk modul penjualan; dua dokumen review kode (arsitektur & risk register); standarisasi mekanisme transaksi berbasis Stored Procedure; alur kerja refactoring; audit endpoint lintas modul; serta dua dokumen UAT (rencana dan hasil eksekusi penjualan domestik). Dokumen ini tidak membuat temuan baru di luar sumber — setiap angka, nama file, dan nama tabel dikutip dari dokumen sumber.

---

## 1. Ringkasan Eksekutif

1. **Modul `penjualan` adalah transaksi kompleks multi-step, bukan CRUD.** Alur `582` berjalan lima tahap: `582spo` (Sales Pre Order, oleh `o_seller`) → `582so` (Sales Order, `o_seller_spv`) → `582pkd` (Pre Packing, `o_gudang`) → `582spd` (Packing List, `o_gudang`) → `582` (Invoice, `o_finance`). Kontrol akses per tahap ditegakkan lewat `alowedAccess()` terhadap `userGroup`.

2. **Sembilan `jenisTr` tercatat untuk `penjualan`**, termasuk `582` (penjualan reguler), `749` (piutang/pelunasan), `982` (retur penjualan dengan pemulihan stok), `1982` (retur non-stok), `382` (POS/tunai langsung), `1582` (indent/booking), `584` (paket/assembling), `1784` (komposit). Modul `pembatalan` menangani `9911` (reversal jurnal non-stok), `9912` (reversal stok), `9749` (write-off piutang buruk); `penjualanproject` menangani `588` dan `1784`; `saham` menangani `6666` dan `757`.

3. **Seluruh mutasi stok melewati satu komponen: `ComLockerStockDualWrite::pair()`.** Pola rezervasi saat item masuk cart adalah `active -N` dan `hold +N` pada tabel `stock_locker` dan `stock_locker_variant`. Saat approval, `hold` dilepas dan `active` dipotong. Modul `pembatalan`, `penjualanproject`, dan `saham` memakai komponen yang sama.

4. **Jurnal dibentuk otomatis oleh `ComJurnal`, bukan oleh kode penjualan langsung.** `FollowUp::doFollowup()` mencatat jurnal Piutang (D), Penjualan (K), HPP (D), Persediaan (K) di dalam `$this->db->trans_start()`. Untuk pembatalan, pembalikan dilakukan oleh `ComTransaksi_jurnal_revert::pair()` yang menukar Debit ↔ Kredit seluruh entri akun transaksi asal ke `com_jurnal`.

5. **Dua dokumen review menemukan risiko struktural yang sama dari sisi berbeda.** `review-modul-penjualan.md` menyoroti duplikasi kode masif (`Create_total.php`, `coTransaksiValues.php`, `__FollowUp.php`, `___Transaksi.php`), kredensial RabbitMQ hardcoded, dan 0% code coverage. `review-risiko-modul-penjualan.md` menyoroti mutasi session dari GET, `save()` yang terlalu besar, dan tiga aksi transisi state (`doFollowup`, `doRevert`, `doCancelPacking`).

6. **Hanya `review-risiko-modul-penjualan.md` yang mencatat progres perbaikan konkret.** Checkpoint 2026-06-13 menyatakan `save()` di `Create.php` sudah dipecah menjadi helper (bootstrap, master identity, detail, registry, payment source, ext step, commit, rollback, cleanup), guard shutdown rollback sudah dipasang, dan cleanup session/locker sudah diseragamkan. Dua item masih terbuka: idempotensi penuh `writePaymentSrc()`/`writeExtStep()` dan audit cabang `save()` yang masih panjang.

7. **Standar ISO/IEC 25010 bersifat retail/POS-oriented, sedangkan implementasinya B2B multi-approval.** Standar menuntut tutup kasir, shift closing, barcode scan < 2 detik, dan mode offline; sementara `review-modul-penjualan.md` mencatat `Transaksi::validate()` > 300 baris, `Create::index()` > 500 baris, `_shoppingCart::viewCart()` > 500 baris. Adaptasi standar ke konteks ERP B2B ini belum didokumentasikan di sumber.

8. **UAT penjualan domestik mencatat satu siklus lengkap di cabang JKT02.** Nomor bukti: `RcS.31.10` (GRN 10 unit `NHT25` HPP Rp 500.000 + 10 unit `FP6SS-ABC40-MGK` HPP Rp 1.320.000), `5822SPO.31.115.1-00021` (subtotal Rp 3.633.182, PPN Rp 399.650, total Rp 4.032.832), `5822pkd.31.115.1` → `5822spd.31.115.1`, lalu `RPC.31.115.1` / `749.31.115.1` dengan saldo piutang akhir Rp 0.

9. **Audit endpoint 2026-04-26 mencatat `penjualan` memiliki 2 isu lokal.** `Transaksi/viewKepoinItems` berstatus `missing-method`, dan `_processSelectBiaya/select` berstatus `missing-controller` pada `application/modules/penjualan/controllers/_processSelectBiaya.php`. Varian `penjualan_non_paket` dan `penjualan_ppn` memiliki pola temuan identik, menandakan masalah berasal dari config bersama.

10. **Arah arsitektur masa depan berkontradiksi dengan implementasi sekarang.** `blueprint_storage_procedure.md` mensyaratkan seluruh transaksi lewat Stored Procedure ACID dengan `CALL sp_ProsesTransaksiPenjualan(...)`, sedangkan implementasi aktual memakai insert langsung ke tabel `transaksi*` dengan pembungkus `trans_start()`/`trans_complete()`. Nama tabel pada dokumen stored procedure (`t_master_barang`, `t_penjualan_nota`) tidak sama dengan skema nyata modul.

---

## 2. Identitas Modul & Klasifikasi Transaksi (`jenisTr`)

### 2.1 Peta Modul

| Modul | Path | Pola Kompleksitas | Ciri Khas |
|---|---|---|---|
| `penjualan` | `application/modules/penjualan/` | Transaksi Kompleks | Multi-variant product picker, CRM order bridge, multi-step approval, dual-write locker, diskon bertingkat, PPN factor, retur |
| `pembatalan` | `application/modules/pembatalan/` | Transaksi Kompleks | Jurnal pembalik otomatis, stock locker reversal, multi-step approval, reverting registry, dukungan multi-variant |
| `penjualanproject` | `application/modules/penjualanproject/` | Transaksi Kompleks | Kontrak project, termijn/progress billing, `_processSelectBiaya` alokasi biaya langsung, `_projectItemEditor` |
| `saham` | `application/modules/saham/` | Transaksi Kompleks | Penyetoran modal saham, pemisahan Agio/Disagio, jurnal paid-in capital |

Workspace yang dipakai blueprint: `new_san_variant`. `blueprint_penjualan.md` menandai standar yang berlaku: *Single Variant Standard & Dual-Write Rollout*, dengan tanggal analisis 2026-07-29.

### 2.2 Tabel Lengkap `jenisTr`

| `jenisTr` | Nama Transaksi | Modul | Sumber |
|---|---|---|---|
| `582` | Penjualan Reguler / Sales Order (Faktur Penjualan) | `penjualan` | `blueprint_penjualan.md` |
| `749` | Piutang Penjualan / Pelunasan Piutang | `penjualan` | `blueprint_penjualan.md` |
| `982` | Retur Penjualan (Sales Return with Stock Restoration) | `penjualan` | `blueprint_penjualan.md` |
| `1982` | Retur Penjualan Non-Stok | `penjualan` | `blueprint_penjualan.md` |
| `382` | Penjualan POS / Direct Cash Sales | `penjualan` | `blueprint_penjualan.md` |
| `1582` | Penjualan Indent / Booking Order | `penjualan` | `blueprint_penjualan.md` |
| `584` | Penjualan Paket / Assembling Sales | `penjualan` | `blueprint_penjualan.md` |
| `1784` | Penjualan Komposit | `penjualan` | `blueprint_penjualan.md` |
| `9911` | Pembatalan Transaksi Jurnal Non-Stok / Reversal Transaksi Umum | `pembatalan` | `blueprint_pembatalan.md` |
| `9912` | Pembatalan Transaksi Stok / Reversal Transaksi Barang & Persediaan | `pembatalan` | `blueprint_pembatalan.md` |
| `9749` | Penghapusan Piutang (Bad Debt Write-off) | `pembatalan` | `blueprint_pembatalan.md` |
| `588` | Penjualan Project / Kontrak Pekerjaan & Pengadaan Project | `penjualanproject` | `blueprint_penjualanproject.md` |
| `1784` | Penjualan Project Komposit | `penjualanproject` | `blueprint_penjualanproject.md` |
| `6666` | Penyetoran Modal Saham (Paid-in Capital Deposit) | `saham` | `blueprint_saham.md` |
| `757` | Revaluasi / Penyesuaian Modal & Agio Saham | `saham` | `blueprint_saham.md` |

> Catatan: `1784` (Penjualan Komposit) tercantum pada dua modul — `penjualan` dan `penjualanproject`. Sumber tidak menjelaskan pembagian tanggung jawab kode `1784` antara keduanya.

### 2.3 Kode Tahap (Step Suffix) untuk `582`

| Kode Step | Nama Step | User Group Pemegang | Sumber |
|---|---|---|---|
| `582spo` | SALES PRE ORDER | `o_seller` | `review-modul-penjualan.md` |
| `582so` | SALES ORDER | `o_seller_spv` | `review-modul-penjualan.md` |
| `582pkd` | PRE PACKING | `o_gudang` | `review-modul-penjualan.md` |
| `582spd` | PACKING LIST | `o_gudang` | `review-modul-penjualan.md` |
| `582` | INVOICE | `o_finance` | `review-modul-penjualan.md` |

Pola nomor dokumen dari bukti UAT_domestik: `5822SPO.31.115.1-00021`, `5822pkd.31.115.1`, `5822spd.31.115.1`, `RPC.31.115.1` (alias `749.31.115.1`). Struktur ini konsisten dengan pemisahan segmen `2` (SPO), segmen cabang `31` (JKT02), ID pihak `115`, dan counter `1`.

### 2.4 Base Controller dan Kontrak Config

Keempat blueprint memakai pola base controller identik:

| Komponen | Nilai / Fungsi |
|---|---|
| Sumber `$this->jenisTr` | `URI segment(4)` |
| Sumber `$this->modul` | `URI segment(1)` |
| Validasi session | `validateUserSession($this->session->login['id'])` |
| Config dimuat | `coTransaksiUi`, `coTransaksiCore`, `coTransaksiLayout`, `coTransaksiValues` |
| Helper dimuat | `he_access_right`, `he_session_replacer`, `he_url` |
| Pemetaan skema DB (`$this->mongoTableList`) | `transaksi`, `transaksi_values`, `transaksi_data`, `transaksi_data_values`, `transaksi_sign`, `transaksi_extstep`, `transaksi_registry` |
| Key session cart | `_TR_[jenisTr]` (contoh: `_TR_582`, `_TR_9911`, `_TR_9912`) |

Pembagian isi config menurut `review-modul-penjualan.md`:

| Config | Isi | Status |
|---|---|---|
| `coTransaksiUi.php` (~3000+ baris) | Definisi UI: steps, fields, selectors, validators per jenis transaksi | Dipakai |
| `coTransaksiCore.php` | Value gates, formulas, processors, value builders | Dipakai |
| `coTransaksiValues.php` | Identik dengan `coTransaksiCore.php` untuk kunci `"582"` | **Ditandai DUPLIKAT** |
| `coTransaksiLayout.php` | Layout / view bindings | Dipakai |
| `_coTransaksiCore.php` | Backup, tidak dipakai | Legacy |

---

## 3. Arsitektur & Alur Utama

### 3.1 Alur Happy Path Penjualan

```text
Step 1: Create   → 582spo  (SALES PRE ORDER)  user: o_seller
Step 2: FollowUp → 582so   (SALES ORDER)      user: o_seller_spv
Step 3: FollowUp → 582pkd  (PRE PACKING)      user: o_gudang
Step 4: FollowUp → 582spd  (PACKING LIST)     user: o_gudang
Step 5: FollowUp → 582     (INVOICE)          user: o_finance
```

Diagram alur refactoring dari `workflow_penjualan.md`:

```text
Inisiasi Draf → Pilih Customer (Selector Pihak) → Tambah Item/Varian
  → Dual-Write Locker → Reservasi Stok (Active -N, Hold +N)
  → Keranjang Belanja → Simpan Draf (Status: Draf)
  → FollowUp Submit → Approval/Finalisasi
  → Approve: Stok Terpotong & Jurnal Akuntansi
```

### 3.2 Peta Controller `penjualan`

| Controller | Fungsi | Method Utama |
|---|---|---|
| `Transaksi.php` | Validasi shopping cart + view listing | `validate()`, `viewUndoneItems()` |
| `Create.php` | Pembuatan transaksi baru (step 1) | `index()`, `preview()`, `save()`, `previewCrm()`, `doRejectCrm()` |
| `Create_total.php` | **DUPLIKAT** `Create.php` (>1000 baris identik) | `index()`, `preview()` |
| `FollowUp.php` | Follow-up ke step berikutnya | `index()`, `doFollowup()`, `doRevert()`, `doCancelPacking()`, `doScan()`, `doDeleteMobile()`, `doReloadSesi()` |
| `__FollowUp.php` | Legacy/backup `FollowUp.php`, masih memuat raw SQL | — |
| `TransaksiCrm.php` | View order dari CRM (estimate) | `viewOrderCrm()`, `checkCustomerRegisterStatus()`, `saveCustomerTo Lithuanian()`* |
| `CustomerApprovalApi.php` | REST API approval customer CRM | `checkStatus()`, `saveCustomer()` |
| `_shoppingCart.php` | Shopping cart engine | `viewCart()`, `addItem()`, `reset()`, `recordFieldElement()`, `recordItemColumn()`, `autoAdjustRoundingAjax()` |
| `_processSelectProduct.php` | Selector produk + dual-write lock | `select()`, `multiSelect()`, `remove()`, `updateValues()` |
| `_processSelectProductKomposit.php` | Varian komposit | — |
| `_processSelectProductPaket.php` | Varian paket (`584`) | — |
| `_processSelectProductPpn.php` | Varian PPN | — |
| `_processPihak.php`, `_processPihakMain.php` | Selector pihak/customer | — |
| `_selectorItem.php`, `_selectorPihak.php`, `_selectorPihakMain.php` | Modal selector | `variantPicker()` pada `_selectorItem` |
| `_processSelectNotaItem.php` | Selector nota item | — |
| `_followupLiveEdit.php` | Live edit saat approval | — |
| `Modul_Controller.php` | Base controller, extends `MX_Controller` | `__construct()` |
| `Rabbitmq.php` | Message queue | `send_task()` |
| `History.php` | Riwayat transaksi | `showData()` |
| `Printing.php` | Cetak dokumen | `preview()`, `printing()`, `viewReceipt()`, `viewProformaReceipt()`, `viewSmallReceipt()` |
| `ViewDetails.php` | Modal detail (multi-variant safe) | `nomer()`, `item_report()` |
| `ActivityReport.php` | Laporan penjualan | — |
| `Debug.php` | — | — |
| `_construct_file.php` | — | — |

\* Nama method pada sumber tertulis `saveCustomerToPihakLain()`; tabel ini menampilkan nama apa adanya.

Model: `Rabbitmq_mdl.php` (koneksi & publish RabbitMQ).

### 3.3 Peta Controller `pembatalan`

| Controller | Fungsi / Method |
|---|---|
| `Create.php` | `index()`, `preview()`, `save()`, `doEdit()`, `preCancelPackingPreview()`, `doPreCancelPacking()` |
| `FollowUp.php` | `index()`, `followupPrePreview()`, `followupPreview()`, `doFollowup()`, `doRevert()`, `doRevertAll()`, `doCancelPacking()` |
| `Transaksi.php` | `index()` (datatable), `validate()` (cek validitas nota via AJAX) |
| `_processSelectNota.php` | `select()`, `remove()`, `updateValues()` — validasi nota asal (`trash_4 = 0`, transaksi asal `completed`) |
| `_processSelectNotaRevert.php` | Handler keterikatan transaksi turunan |
| `_processSelectProduct.php` | `select()`, `multiSelect()`, `remove()` — pembatalan parsial per-item |
| `_shoppingCart.php` | `viewCart()`, `reset()`, `recordFieldElement()`, `recordItemColumn()`, `recordPairedItem()` |
| `ViewDetails.php` | `nomer()`, `item_report()` dengan key `{produk_id}_{variant_id}` |
| `Printing.php` | `viewReceipt()`, `viewProformaReceipt()`, `viewReceiptCashIn()` |
| `ActivityReport.php`, `History.php` | `viewMonthly()`, `viewDaily()`, `viewHistory()` |

### 3.4 Sekuensi Eksekusi `doFollowup()`

| Urutan | Aksi | Komponen / Tabel |
|---|---|---|
| 1 | Update status transaksi menjadi `completed` | tabel `transaksi` |
| 2 | Jurnal otomatis: Piutang (D), Penjualan (K), HPP (D), Persediaan (K) | `ComJurnal` |
| 3 | Stock release / dual-write: `hold` → `-hold` & `-active` (penjualan) atau `+active` (retur) | `ComLockerStockDualWrite::pair()` |
| 4 | Update registry step | `transaksi_registry` |
| 5 | Output | JSON status sukses + opsi cetak faktur |

Rantai identik berlaku di `penjualanproject` (tambah jurnal Pendapatan Kontrak & HPP Proyek) dan `saham` (jurnal Kas/Bank D vs Modal Saham Disetor K + Agio Saham K bila ada).

Varian pembatalan: `ComTransaksi_jurnal_revert::pair()` membalik seluruh entri akun transaksi asal ke `com_jurnal` dengan penukaran Debit ↔ Kredit, kemudian `ComLockerStockDualWrite::pair()` mengembalikan stok (hanya untuk `jenisTr = 9912`), lalu `transaksi_registry` pada nota asal ditandai `REVERTED` / `CANCELED`.

### 3.5 View Layer

| View | Peran |
|---|---|
| `views/transaksi_modul.php` | UI shell |
| `views/variant_picker.php` | Modal matrix varian (ukuran × warna) dengan cek stok real-time `stock_locker_variant` |
| `views/create_preview_crm_variant_picker.php` | UI pemetaan varian CRM |
| `views/shoppingCart.php` | Tabel cart |
| `views/printing.php` | Template cetak faktur |
| `views/transaksi.php`, `create.php`, `followUp.php`, `history.php`, `viewdetails.php` | View utama modul `penjualan` |
| `template/transaksi_extern.html` | Shell untuk Step 2 preview |
| `template/` | Kumpulan template HTML untuk printing |

### 3.6 Dual-Write Stock Locker

Aturan yang berulang di keempat blueprint:

1. Penulisan selalu dua tabel: `stock_locker` dan `stock_locker_variant`.
2. Pemanggilan selalu lewat `ComLockerStockDualWrite::pair()`.
3. **Sentinel varian:** `variant_id = 0` (produk tanpa varian) dikonversi menjadi `1` sebelum ditulis ke `stock_locker_variant`.
4. Rezervasi saat memilih barang: `active -N`, `hold +N`.
5. Pelepasan saat approval: `hold` dilepas, `active` dipotong.
6. `reset()` pada `_shoppingCart.php` melepas hold yang tertinggal saat keranjang dikosongkan.

---

## 4. Fungsi, Method & Logika Bisnis Penting

### 4.1 `Modul_Controller::__construct()`

Membaca `URI segment(4)` sebagai `jenisTr`, memvalidasi session login lewat `validateUserSession()`, memuat empat file config transaksi, memuat tiga helper (`he_access_right`, `he_session_replacer`, `he_url`), dan menginisialisasi `$this->mongoTableList` dengan tujuh tabel skema `transaksi*`.

### 4.2 `Create.php` — Entry Transaksi

| Method | Input | Validasi | Query / Tulis |
|---|---|---|---|
| `index()` | filter customer, sales ID, `jenisTr` | inisialisasi session cart `_TR_[jenisTr]` | `MdlCustomer`, `MdlProduk`, `MdlCabang` |
| `preview()` | session cart | kelengkapan `pihakID`, cart tidak kosong, limit kredit customer | `MdlMongoMother`/`MdlTransaksi` untuk HPP & jurnal |
| `save()` | POST: catatan, alamat pengiriman, diskon total, PPN, cara bayar | form rules `pihakID` wajib, `item` minimal 1 | insert `transaksi`, `transaksi_data` (termasuk `variant_id`, `variant_nama`), `transaksi_values` (total, DPP, PPN, HPP), `transaksi_registry` |
| `syncPihakMainExecFromRequest()` | `$_GET` |see P0 risk | mutasi session `_TR_[jenisTr]['main']['pihakMainExec']` |
| `updateMainSession()` | `$_GET["id"]` |_line 18067_ | langsung menulis session dari GET |
| `writeDataRegistries()` | — | _line 3979_ | tulis registry |
| `writePaymentSrc()` | payload | validasi `extern_label2` (_lines 3745, 4009, 7216, 7977_) | tulis payment source |
| `writeExtStep()` | — | _lines 4061, 10836, 16081_ | tulis `transaksi_extstep` |
| `previewCrm()` | JSON CRM bridge | resolve customer via `MdlCustomer` | baca `customer_id` dari bridge table |
| `previewCrmVariantPicker()` | JSON CRM | — | petakan produk CRM ke `produk_id` + `variant_id` |
| `savePreviewCrmVariant()` | JSON CRM | — | simpan audit trail `preview_crm_variant_audit` |
| `doRejectCrm()` | — | — | tolak order CRM |

### 4.3 Helper Baru di `Create.php` (hasil refactor)

| Helper | Tugas |
|---|---|
| `prepareSaveBootstrapContext()` | setup awal sebelum fase save |
| `guardSaveTransactionContext()` / `resolveSaveSessionContext()` / `validateSaveAgainstDbState()` | guard 3-way matching (input user ↔ session ↔ DB) |
| `validateSavePreflightContext()` | preflight awal `save()` |
| `finalizeSaveMasterIdentity()` | finalisasi nomor & identitas master |
| `persistSaveDetailMainPayloads()` | fase detail utama |
| `persistSaveDetailAuxPayloads()` | fase payload detail tambahan |
| `persistSavePhysicalRegistryPayloads()` | registry fisik & turunan item |
| `persistSaveMainInputPayloads()` | fase `main_inputs` yang memicu payment source & ext step |
| `persistSaveDataRegistries()` | registry step |
| `persistSavePaymentSource()` | payment source standar + cabang `_key`/`main_inputs` + override field |
| `persistSaveExtStep()` | `transaksi_extstep` |
| `executeSaveComponentProcessor()` | eksekusi model `Com*` yang berulang |
| `abortSaveTransaction()` | rollback terpusat (menggantikan `matiHEre()`) |
| `finalizeSaveCommitFlow()` | commit + cleanup session + output sukses |
| `emitSaveSuccessOutput()` | output akhir `save()` |
| `cleanupSaveSessionState()`, `resetSaveRuntimeLocks()` | battlefield session & locker |

Tiga helper per-phase sudah diimplementasikan, sisanya (`commitSaveTransaction()`, `rollbackSaveTransaction()`, `writeSaveMaster()`, `writeSaveSignature()`, `validateSaveWriteResult()`, `verifySaveConsistency()`, `reportSaveFailure()`, `buildSaveFailureResponse()`) masih **rencana** dalam dokumen.

### 4.4 `FollowUp.php` — Approval & Execution

| Method | Baris (per risk register) | Perilaku |
|---|---|---|
| `doFollowup()` | 14241 | transisi state utama: status, jurnal, locker release, registry |
| `doRevert()` | 19203 | batalkan proses; irmã pada `pembatalan` juga punya `doRevertAll()` |
| `doCancelPacking()` | 31920 | revert status pengiriman/packing |
| `doScan()` | 5676 | endpoint mobile scan |
| `doDeleteMobile()` | 7553 | endpoint mobile hapus item |
| `doReloadSesi()` | 7602 | endpoint mobile reload session |
| `followupPrePreview()`, `followupPreview()` | 11104 | preview approval; bergantung `step_number` dan `main` dari session |

Negara akhir yang ditulis ke DB: `completed` / `canceled` pada `transaksi`.

### 4.5 `_processSelectProduk` & Variant Picker

- `_processSelectProduct.php`: `select()`, `multiSelect()`, `remove()`, `updateValues()` — mengunci stok `hold` pada varian terpilih.
- `_selectorItem.php::variantPicker()`: modal matriks varian (ukuran, warna, grade) dengan pengecekan stok varian real-time di `stock_locker_variant`; mendukung fallback sentinel `1` bila `variant_id = 0`.
- `_selectorItem.php::selectItem()`: memasukkan produk + varian ke `_TR_[jenisTr]`.
- `ViewDetails.php`: key baris unik `{produk_id}_{variant_id}` (contoh implementasi: `$row['produk_id'] . '_' . $row['variant_id']`) agar varian berbeda tidak saling overwrite; badge label `variant_label` / `variant_nama`.

### 4.6 Integrasi CRM & Root Cause Customer Mismatch

Sistem identitas customer per entitas:

| Entitas | Tabel | Primary Key | Contoh |
|---|---|---|---|
| CRM (`san_ibb_master`) | `leads` / `customers` | `lead_id` atau `client_id` | `"lead_xyz_789"` |
| SAN (Holding) | `per_customers` | `id` auto-increment | `123` |
| Subsidiary (`san_sarana_8apr`) | `per_customers` | `id` auto-increment | `456` |

Bridge table `penjualan_transaksi_data_crm_bridge` (model `MdlCrmDataBridge`) memuat:

| Kolom | Makna |
|---|---|
| `client_id` | `lead_id` dari CRM (identifier unik lintas sistem) |
| `customer_id` | `id` dari `per_customers` SAN (integer auto-increment) |
| `referensi_id` | `estimate_id` dari CRM |

Kode terkait: `MdlTransaksiCrm::resolveCustomerName()` baris 335 (dua skenario: `customer_id` terisi → lookup `per_customers` SAN; `customer_id = 0` tapi `client_id` ada → lookup via `client_id`), `TransaksiCrm.php::viewOrderCrm()` baris 93, `Create.php::previewCrm()` baris ~12264–12764, dan `MdlSubsidiary` dengan `tableName = "per_customers"` serta filter `status='1'`, `trash='0'`, `member_id='100'`.

**Akar masalah:** ID auto-increment milik SAN dipakai langsung di subsidiary tanpa cross-reference mapping, sehingga transaksi bisa menunjuk customer yang salah, error, atau ambigu. **Solusi yang direkomendasikan (P0):** tabel `customer_cross_reference` dengan kolom `client_id` (unik), `san_customer_id`, `subsidiary_customer_id`, `subsidiary_db` (default `san_sarana_8apr`), `nama_customer`, `created_at`, `updated_at`, plus index `idx_san_customer` dan `idx_subsidiary_customer`. Alternatif: kirim `client_id` alih-alih `customer_id` ke subsidiary, atau sediakan UI manual mapping.

### 4.7 Modul `penjualanproject`

- `Create::index()` — header project: customer, nilai kontrak, lokasi project, target selesai.
- `Create::preview()` — rincian RAB (Rencana Anggaran Biaya), alokasi material/varian, jurnal proyek.
- `_projectItemEditor.php` — ubah harga, volume, dan alokasi varian per sub-pekerjaan.
- `_processSelectBiaya.php` — `select()`, `remove()`, `updateValues()` untuk biaya operasional langsung (sewa alat, subkontraktor, transportasi) yang dibebankan ke HPP project.
- `_shoppingCart.php::reset()` — kosongkan cart dan lepas hold material project.

### 4.8 Modul `saham`

`_processSelectRekening.php` menerima nama pemegang saham, jumlah lembar ditambah, nilai nominal per lembar, dan harga setor; menghitung Modal Disetor serta selisih Agio/Disagio. `FollowUp::doFollowup()` memperbarui struktur persentase pada register pemegang saham, memanggil `ComJurnal` untuk Kas/Bank (D) vs Modal Saham Disetor (K) + Agio Saham (K bila ada), lalu menulis `transaksi_registry`.

---

## 5. Aturan Validasi, Diskon, PPN, dan Stok Locker

### 5.1 Validasi yang Teridentifikasi di Kode

| Area | Aturan | Sumber |
|---|---|---|
| Session & akses | `validateUserSession($this->session->login['id'])`; `he_access_right`; `alowedAccess()` per `userGroup` step | blueprint + review-modul |
| Form penjualan | `pihakID` wajib; `item` minimal 1 | `Create::save()` |
| Limit kredit | dicek di `Create::preview()` dan `FollowUp::followupPreview()`; approval group customer | blueprint |
| Kunci transaksi | `doFollowup()` me-lock transaksi agar tidak eksekusi ganda | blueprint |
| Nota asal (pembatalan) | `trash_4 = 0` (belum pernah dibatalkan) dan transaksi asal `completed` | `blueprint_pembatalan.md` |
| Edit draf pembatalan | hanya saat `status_next` = step 1 | `Create::doEdit()` |
| Keterikatan turunan | faktur penjualan yang sudah dilunasi tidak bisa dibatalkan sebelum pembayaran dibatalkan | `_processSelectNotaRevert` |
| Stok varian | cek real-time `stock_locker_variant` saat picker dibuka | `_selectorItem::variantPicker()` |
| Konsistensi 3 sumber | guard wajib membandingkan input user, session transaksi, state database; hentikan dengan status `VOID` atau `INDETERMINATE` | `review-risiko-modul-penjualan.md` bagian A |
| Kompatibilitas | PHP 5.6 + CodeIgniter 3; gunakan `array()`, hindari syntax PHP 7/8 | risk register bagian E & H |

### 5.2 Diskon

`blueprint_penjualan.md` mencatat perhitungan diskon kompleks berupa kombinasi **diskon 1 + diskon 2 + diskon nominal**, dengan penyesuaian pembulatan harga otomatis melalui `autoAdjustRoundingAjax()` dan `recordItemColumn()` di `_shoppingCart.php`. Batas wewenang diskon per role/per produk, price floor (harga jual ≥ HPP + mark-up minimum), price ceiling (HET), dan larangan stacking diskon ganda diatur sebagai standar wajib PRC-01 s.d. PRC-08 dalam `STANDAR_MODUL_PENJUALAN_ISO25010.md`, tetapi **belum ada bukti implementasi kontrol tersebut di kode modul** pada sumber.

Payload contoh `blueprint_penjualan.md` memperlihatkan bentuk item:

```json
{
  "cart_key": "prod_210_variant_5",
  "produk_id": 210,
  "variant_id": 5,
  "variant_nama": "Size XL - Hitam",
  "nama": "Jaket Safety High-Vis",
  "satuan": "Pcs",
  "jumlah": 10.0,
  "harga": 350000.0,
  "diskon_persen": 5.0,
  "subtotal": 3325000.0
}
```

### 5.3 PPN

- `Create::save()` menerima POST berisi pajak PPN; `transaksi_values` menyimpan DPP, PPN, dan total.
- Modul `penjualan` punya controller khusus varian PPN: `_processSelectProductPpn.php`; modul kembar `penjualan_ppn` tercatat di audit endpoint.
- **Perbedaan angka PPN antar sumber (lihat bagian 9):** payload contoh blueprint memakai `total_dpp = 3022727.0`, `total_ppn = 302273.0`, `total_grand = 3325000.0` — rasio PPN 10%. Bukti UAT memakai PPN 11%: DPP Rp 3.633.182 × 11% = Rp 399.650, total Rp 4.032.832.

### 5.4 Standar Proteksi Stok & Alokasi (ISO 25010)

| ID | Aturan | Metode Verifikasi |
|---|---|---|
| INV-01 | Tolak SO jika qty melebihi available stock, kecuali pre-order/dropship aktif | Uji order melebihi stok |
| INV-02 | Cek stok real-time (bukan cache) saat pembuatan SO | Uji 2 user order produk sama |
| INV-03 | Alokasi sementara (*soft lock/reserve*) dengan masa berlaku | Uji 2 salesman order bersamaan |
| INV-04 | Alokasi kedaluwarsa dilepas otomatis (default 2 jam) | Uji simulasi SO hangus |
| INV-05 | Aturan prioritas alokasi (premium/FIFO) | Uji skenario prioritas |
| INV-06 | DO reguler hanya jika stok fisik setelah dikurangi alokasi cukup | Uji kirim tanpa stok |
| INV-07 | Satu DO hanya dari satu gudang | Uji DO multi-gudang |

Tidak ada di sumber yang menyatakan INV-04 (expiry 2 jam) sudah diimplementasikan; pola yang ada justru *hold dilepas saat approval*.

### 5.5 Standar Proteksi Pengiriman & Tagihan

| ID | Aturan | Parameter Default |
|---|---|---|
| OPS-01 | Toleransi over delivery | 5% |
| OPS-02 | Toleransi under delivery perlu approval manajemen | 10% |
| OPS-03 | Over delivery hanya role supervisor gudang/manajer + audit trail | — |
| OPS-04 | Wajib isi alasan saat over/under kirim | — |
| OPS-05 | Toleransi over billing | 0% |
| OPS-06 | Over billing hanya finance manager + audit trail | — |
| OPS-07 | Konsistensi harga SO = DO = Invoice | default konsisten wajib |

### 5.6 Standar Harga, Diskon, dan Kredit

| ID | Aturan | Standar |
|---|---|---|
| PRC-01 | Batas diskon per role | kasir maks 5%, supervisor maks 15% |
| PRC-02 | Batas diskon per produk | boleh berbeda dari default role |
| PRC-03 | Diskon melebihi wewenang memicu approval workflow | sebelum simpan |
| PRC-04 | Peringatan margin minimum | harga jual − HPP |
| PRC-05 | Larangan stacking diskon ganda; hitung bertingkat dengan benar | tidak sekadar dijumlah |
| PRC-06 | Harga jual ≥ HPP + mark-up minimum | — |
| PRC-07 | Harga jual ≤ HET | bila ada |
| PRC-08 | Harga kontrak pelanggan dipakai AUTOMATIS; input manual kasir dilarang | — |
| CL-01 | Validasi plafon kredit = piutang berjalan + nilai baru; menolak bila lewat | — |
| CL-02 | Pelanggan "Ditangguhkan" (tunggakan > 90 hari) diblokir | semua transaksi baru |
| CL-03 | Notifikasi saat saldo mencapai 80% plafon | email/dashboard |
| CL-04 | Maksimum jatuh tempo | 30 hari grosir, 0 hari ritel tunai |
| CL-05 | Uang muka make-to-order minimal 50% sebelum SO diproses | — |
| CL-06 | Blokir kredit jika ada faktur lewat jatuh tempo > 60 hari | — |

### 5.7 Standar Jual Tanpa Stok

| ID | Aturan |
|---|---|
| DPS-01 | Dropship: SO tanpa mengurangi stok gudang, auto-create PO ke vendor, barang dikirim vendor→customer |
| DPS-02 | Pre-order: SO diterima meski stok kosong, catat estimasi tanggal ketersediaan |
| DPS-03 | DO dropship hanya setelah PO vendor dikonfirmasi; stok fisik perusahaan tidak berkurang |
| DPS-04 | DO pre-order hanya saat stok fisik tersedia; notifikasi customer saat stok tersedia |
| DPS-05 | Dropship wajib menyimpan referensi PO; pre-order wajib menyimpan estimasi tanggal stok |
| DPS-06 | Akuntansi khusus: pendapatan dari penjualan ke customer, HPP dari pembelian vendor, stok tidak pernah masuk persediaan |

Implementasi dropship/pre-order **tidak ditemukan** pada blueprint modul mana pun yang menjadi sumber.

### 5.8 Larangan Teknis (Release Gate)

| ID | Larangan | Konsekuensi |
|---|---|---|
| L-01 | Hard delete data transaksi | GAGAL REVIEW |
| L-02 | Password plain text di database | GAGAL REVIEW |
| L-03 | Negative stock tanpa flag khusus | GAGAL REVIEW |
| L-04 | Tipe data float untuk uang | GAGAL REVIEW |
| L-05 | Crash/force close saat input salah | WAJIB FIX |
| L-06 | Satu user punya create + approve SO | GAGAL REVIEW |
| L-07 | Over billing tanpa approval | GAGAL REVIEW |
| L-08 | Kirim tanpa stok fisik untuk penjualan reguler | GAGAL REVIEW |

L-04 relevan langsung dengan JSON payload blueprint yang memakai angka desimal (`350000.0`, `10.0`) dan kolom `diskon_persen` bertipe persen.

---

## 6. Risiko, Bug, dan Temuan Audit

### 6.1 Persistent Risks (`review-modul-penjualan.md`, 9 Mei 2026)

| Risiko | Detail | Dampak |
|---|---|---|
| Duplikasi kode masif | `Create.php` vs `Create_total.php` (>1000 baris identik); `coTransaksiCore.php` vs `coTransaksiValues.php` (~300 baris identik untuk key `"582"`); `FollowUp.php` vs `__FollowUp.php`; `Transaksi.php` vs `___Transaksi.php` | Fix harus diubah di 2–3 tempat |
| Session sebagai "database" sekunder | `$_SESSION[$cCode]` dengan `$this->cCode = "_TR_" . $this->jenisTr`, dipakai > 100 tempat (`main_elements`, `items`, `main.pihakMainExec`) | Tidak scalable untuk load-balanced, sulit unit test, session collision multi-tab, data hilang saat session expire |
| Kredensial RabbitMQ hardcoded | `penjualan/models/Rabbitmq_mdl.php` baris ~15; koneksi ke host internal `192.168.5.14` port `5672` dengan kredensial literal (redaksi nilai kredensial demi keamanan) | Eksposur di VCS, tidak bisa berbeda per environment, rotasi sulit |
| Legacy `__FollowUp.php` | Raw SQL: `UPDATE transaksi SET indexing_main_values = '$indexing' WHERE id = '$masterID'` | Potensi SQL injection |
| Tidak ada unit/E2E test | Tidak ditemukan folder `tests/`; code coverage 0% | "Blind deployment" tanpa safety net |

### 6.2 Latent Risks (`review-modul-penjualan.md`)

| Kode | Risiko | Lokasi |
|---|---|---|
| Risk A | Race condition multi-step: A dan B follow-up transaksi sama, tidak ada pessimistic/optimistic locking | `FollowUp::doFollowup()`, `doCancelPacking()` |
| Risk B | Tidak ada centralized logging (`error_log()`/`log_message()` tidak konsisten) | seluruh modul |
| Risk C | Performa cart besar: `preview()` looping O(n×m) atas `items`, `items2`, `items2_sum`, `items3_sum`; ada N+1 query | `_shoppingCart.php`, `Transaksi::preview()` |
| Risk D | Tidak ada CORS, CSRF tidak konsisten, tidak ada rate limiting, tidak ada request logging | `CustomerApprovalApi.php`, `Rabbitmq.php` |
| Risk E | `coTransaksiUi.php` ~3000+ baris dalam satu array; tidak ada inheritance antar jenisTr; syntax error satu jenisTr merusak seluruh modul | config |
| Risk F | `die()` / `mati_disini()` di produksi, misal `die("konfigurasi transaksi belum ditentukan @" . __LINE__)` | >10 kemunculan |
| Risk G | SQL injection di `__FollowUp.php` | legacy |
| Risk H | Tidak ada metrics endpoint, health check, structured logging | seluruh modul |

### 6.3 Risk Register Terurut Prioritas (`review-risiko-modul-penjualan.md`)

| Prioritas | Temuan | Lokasi | Jenis |
|---|---|---|---|
| P0 | Mutasi session dari GET | `Create::syncApparentlyFromRequest()` line 73; `updateMainSession()` line 18067 | Persisten |
| P0 | `save()` terlalu besar & banyak cabang | `Create::save()` line 1444 | Laten |
| P0 | Follow-up action mengubah state | `FollowUp::doFollowup()` 14241, `doRevert()` 19203, `doCancelPacking()` 31920 | Laten |
| P1 | Session jadi sumber kebenaran preview | `Create` line 871, `FollowUp` line 11104 | Persisten |
| P1 | Banyak endpoint mobile menulis state sama | `doScan()` 5676, `doDeleteMobile()` 7553, `doReloadSesi()` 7602 | Persisten |
| P1 | Penulisan data pendamping di beberapa cabang | `writeDataRegistries()` 3979; `writePaymentSrc()` 3745/4009/7216/7977; `writeExtStep()` 4061/10836/16081 | Laten |
| P1 | Commit/cleanup session tidak seragam | `Create.php` & `FollowUp.php` | Persisten |
| P2 | Connector/cabang memperbanyak state turunan | `connectTo` & cloning session di `save()` | Laten |
| P2 | Validasi tersebar, bukan satu pintu 3-way matching | preview, save, follow-up, revert, cancel, mobile | Laten |
| P2 | Endpoint administratif menyentuh state transaksi | `updateMainSession()` line 18067 | Persisten |
| P2 | Mobile flow rawan retry & request dobel | `doScan()`, `doDeleteMobile()`, `doReloadSesi()` | Laten |
| P3 | Observabilitas rendah | `Create.php` & `FollowUp.php` | Laten |

### 6.4 Rekomendasi Prioritas dari Review Arsitektur

| Priority | Action | File | Estimasi |
|---|---|---|---|
| P0 | Hapus duplikat & update references | `Create_total.php`, `coTransaksiValues.php`, `___Transaksi.php`, `__FollowUp.php` | 1 hari |
| P0 | Pindahkan kredensial RabbitMQ ke config/`.env` | `models/Rabbitmq_mdl.php` | 1 jam |
| P0 | Review & fix SQL injection | `__FollowUp.php` | 2 jam |
| P1 | DB-level locking di `doFollowup()` | `FollowUp.php` | 1 hari |
| P1 | Centralized logging di semua entry point | semua controller | 1–2 hari |
| P1 | Refactor session-dependent code ke cache/DB driver | semua controller | 3–5 hari |
| P2 | Pecah `coTransaksiUi.php` per jenis transaksi | config | 2 hari |
| P2 | Ganti `die()` dengan exception handler + friendly error page | semua controller | 1 hari |
| P2 | CSRF + rate limiting di API endpoints | `CustomerApprovalApi.php`, `Rabbitmq.php` | 1 hari |
| P3 | Pecah `Transaksi::validate()` per validator class | `Transaksi.php` | 2–3 hari |
| P3 | Unit test critical path (`doFollowup`, `save`) | `tests/` baru | 3–5 hari |

### 6.5 Audit Endpoint Lintas Modul (2026-04-26)

Audit memeriksa kunci `selectorCaller`, `selectorProcessor`, `selectorProcessorBi`, `printLocation`, `receiptTemplate` pada modul yang memiliki `coTransaksiUi.php` dan/atau `coTransaksiLayout.php`, dengan route yang sudah diperbaiki: `Transaksi/viewUndoneItemsIndex`, `History/showData`, `Transaksi/viewKepoinItems`. Prefix eksternal yang tidak diadu sebagai controller lokal: `Selectors`, `ValueGate`, `Addons`, `Tools`.

Ringkasan untuk keluarga penjualan:

| Modul | UI | Layout | selectorCaller | selectorProcessor | selectorProcessorBi | printLocation | receiptTemplate | Undone | History/showData | Kepoin | Local Issues |
|---|---|---|---:|---:|---:|---:|---|---|---|---|---:|
| `penjualan` | Y | Y | 12 | 12 | 0 | 7 | Y | ok | ok | missing-method | 2 |
| `penjualan_non_paket` | Y | Y | 11 | 11 | 0 | 11 | Y | ok | ok | missing-method | 2 |
| `penjualan_ppn` | Y | Y | 12 | 12 | 0 | 12 | Y | ok | ok | missing-method | 2 |
| `penjualanproject` | Y | Y | 2 | 2 | 0 | 2 | Y | ok | ok | missing-method | 1 |
| `penjualanreseller` | Y | Y | 4 | 4 | 0 | 3 | Y | ok | ok | missing-method | 1 |
| `pembatalan` | Y | Y | 3 | 3 | 0 | 3 | Y | ok | ok | ok | 0 |
| `settlement` | Y | Y | 1 | 1 | 0 | 2 | Y | missing-controller | missing-controller | missing-controller | 7 |

Detail temuan lokal untuk keluarga penjualan:

| Modul | Type | Endpoint | Status | Method | Occ | Controller File |
|---|---|---|---|---|---:|---|
| `penjualan` | fixed:Transaksi/viewKepoinItems | `Transaksi/viewKepoinItems` | missing-method | `viewKepoinItems` | 1 | `application/modules/penjualan/controllers/Transaksi.php` |
| `penjualan` | selectorProcessor | `_processSelectBiaya/select` | missing-controller | `select` | 1 | `application/modules/penjualan/controllers/_processSelectBiaya.php` |
| `penjualan_non_paket` | fixed:Transaksi/viewKepoinItems | `Transaksi/viewKepoinItems` | missing-method | `viewKepoinItems` | 1 | `.../penjualan_non_paket/controllers/Transaksi.php` |
| `penjualan_non_paket` | selectorProcessor | `_processSelectBiaya/select` | missing-controller | `select` | 1 | `.../penjualan_non_paket/controllers/_processSelectBiaya.php` |
| `penjualan_ppn` | fixed:Transaksi/viewKepoinItems | `Transaksi/viewKepoinItems` | missing-method | `viewKepoinItems` | 1 | `.../penjualan_ppn/controllers/Transaksi.php` |
| `penjualan_ppn` | selectorProcessor | `_processSelectBiaya/select` | missing-controller | `select` | 1 | `.../penjualan_ppn/controllers/_processSelectBiaya.php` |
| `penjualanproject` | fixed:Transaksi/viewKepoinItems | `Transaksi/viewKepoinItems` | missing-method | `viewKepoinItems` | 1 | `.../penjualanproject/controllers/Transaksi.php` |
| `penjualanreseller` | fixed:Transaksi/viewKepoinItems | `Transaksi/viewKepoinItems` | missing-method | `viewKepoinItems` | 1 | `.../penjualanreseller/controllers/Transaksi.php` |

Catatan: modul `settlement` adalah yang paling bermasalah secara keseluruhan (7 local issues, seluruhnya `missing-controller`), termasuk `Printing/viewReceiptReg`, `_selectorItem/selectItem`, dan `_processSelectNota/select`.

---

## 7. UAT dan Standar Kualitas

### 7.1 UAT Plan (TASK-13)

| Aspek | Isi |
|---|---|
| Library teruji | `LibPenjualanCreateSave`, `LibPenjualanFollowUp` |
| Target | Query builder mengikuti standard CI 3 / PHP 5.6; *binding* mencegah SQL injection; semua raw query sudah dikonversi (TASK-11) |
| Hasil | Tidak ada *Fatal Error* atau *Query Exception* saat eksekusi pembuatan faktur varian |
| Prasyarat UAT manual | User dengan akses *sales* / *manager* |
| Test case manual | 1) Login `sales`; 2) buka form Penjualan, pilih Customer, tambah Item (varian & master); 3) Submit; 4) login `manager`, approval |
| Expected result | `stock_locker_variant` untuk varian `hold` menjadi nol dan `active` terpotong; dokumentasi tercetak sesuai data DB |

Cross-reference aktivitas: transaksi modul penjualan terekam pada tabel `log` dengan `jenisTr` terkait; ekstraksi library dilakukan ke `LibPenjualanCreateSave` dan `LibPenjualanFollowUp`.

### 7.2 Hasil UAT Penjualan Domestik / Lokal (18 September 2026)

Lingkungan: 100% cabang JKT02 (`cabang_id: 31`), tanpa callout API ke SAN.

Perbedaan alur SAN vs Domestik:

| Aspek | Alur SAN (Pusat) | Alur Domestik (Mandiri) |
|---|---|---|
| Sifat alur | Terhubung Data Center, terotomatisasi | Mandiri, manual penuh |
| Purchase Order | Auto PO ke SAN | Manual oleh cabang ke vendor lokal |
| GRN | Auto GRN kiriman SAN | Manual oleh gudang cabang saat barang tiba |
| Distribusi stok | Auto distribusi ke gudang | Auto create request distribusi internal cabang |
| Prepacking | Auto prepacking | Dijalankan dan dikonfirmasi di level cabang |
| Hutang/Piutang | Mutasi antar-cabang (intercompany) | Hutang murni ke supplier pihak ketiga |

Tiga alasan bisnis UAT ini: (1) pemisahan entitas untuk menghindari transaksi intercompany ke SAN; (2) integritas HPP agar terbentuk murni dari faktur pembelian vendor lokal, bukan transfer price SAN; (3) pencegahan trigger `sellerDcGuard` / `sellerDcID` yang mensyaratkan relasi akun data center SAN.

Rekonsiliasi bertahap:

| Tahapan | Nomor Bukti | Waktu | Pelaksana | Status |
|---|---|---|---|---|
| 1 & 2 Pengadaan & GRN | `RcS.31.10` | 12:30 | Logistik Cabang | DONE |
| 3 Distribusi stok | `RcS.31.10` | 12:30 | Sistem Internal | DONE |
| 4 Sales Order (cart) | `5822SPO.31.115.1-00021` | 12:31 | Yanty (Sales Admin) | DONE |
| 5 Prepacking | `5822pkd.31.115.1` → `5822spd.31.115.1` | 12:41–12:42 | Superadmin | DONE |
| 6 Settlement & Kas | `RPC.31.115.1` (`749.31.115.1`) | 13:07 | Yanty (Kasir/Finance) | DONE |

Rincian financially: penerimaan 10 unit `NHT25` (HPP Rp 500.000) dan 10 unit `FP6SS-ABC40-MGK` (HPP Rp 1.320.000); customer **Wanda Dwiana Putri** (ID 115); subtotal Rp 3.633.182 + PPN Rp 399.650 = Rp 4.032.832; stok masing-masing berkurang 1 unit (sisa 9); Piutang Usaha Lokal Rp 4.032.832 terbentuk lalu *auto settle* dengan saldo akhir Rp 0.

Analisis margin: HPP produk terjual Rp 1.820.000; laba kotor Rp 1.813.182 dengan margin ~49,9%.

### 7.3 Standar Kualitas ISO/IEC 25010 — Ringkasan

| Karakteristik | ID | Ambang Wajib |
|---|---|---|
| Functional Suitability | FS-01 | Modul wajib punya input transaksi, cetak struk/invoice, retur & nota kredit, laporan (harian/bulanan/tahunan), tutup kasir (shift closing) |
| | FS-02 | Perhitungan 100% akurat, toleransi 0%, uji 50 skenario |
| | FS-03 | Fitur sesuai jenis bisnis (retail/grosir/restoran) |
| Reliability | RL-01 | Crash rate < 1 per 1000 transaksi; tanpa memory leak setelah 8 jam |
| | RL-02 | Uptime ≥ 99,5% saat jam operasional |
| | RL-03 | Mode offline: transaksi tetap jalan saat internet putus, sinkronisasi otomatis, tanpa duplikat/kehilangan data |
| | RL-04 | Recoverability < 2 menit |
| Performance | PE-01 | Scan barcode → produk < 2 detik; simpan → struk < 1 detik; laporan bulanan 1.000+ transaksi < 5 detik |
| | PE-02 | RAM maks 512 MB (mobile) / 256 MB (web); cache < 500 MB |
| | PE-03 | ≥ 100 transaksi/menit per kasir; 500.000+ histori tanpa degradasi |
| Usability | US-01 | Kasir baru tanpa pelatihan formal < 10 menit per transaksi |
| | US-02 | Tombol ≥ 48×48 px; hijau = aksi positif, merah = batal; shortcut F1 bantuan, F2 scan, F3 cetak |
| | US-03 | Sistem menolak diskon di luar wewenang, pembayaran kurang dari total, void tanpa alasan |
| | US-04 | Mode kontras tinggi untukDetectivate (tender pemerintah), WCAG 2.1 level A |
| Security | SC-01 | Invoice APPROVED tidak bisa diubah; audit trail merekam siapa/kapan/nilai lama/baru |
| | SC-02 | Nomor unik + timestamp di-*hash*; kasir tidak bisa hapus log |
| | SC-03 | Void/refund/edit harga wajib otorisasi supervisor |
| | SC-04 | Password min 6 karakter alfanumerik atau biometrik; auto logout saat shift berakhir |
| | SC-05 | Data pelanggan dibatasi per role (kasir hanya nama & nota) |
| Compatibility | CP-01 | Integrasi Stok + Akuntansi + Pelanggan, dipanggil < 1 detik per transaksi sukses |
| Maintainability | MT-01 | Retur, laporan, payment sebagai modul terpisah |
| | MT-02 | Setiap exception tercatat di log: file, baris, timestamp, stack trace |
| | MT-03 | Metode pembayaran baru (QRIS, crypto) cukup 1 class/function |
| Portability | PT-01 | Instalasi < 3 menit |
| | PT-02 | Responsif 1024×768, 800×1280, 360×640 |
| Quality in Use | QU-01 | ≥ 98% transaksi berhasil tanpa pembatalan/pengulangan |
| | QU-02 | Rata-rata waktu proses ≤ 60 detik |
| | QU-03 | Kepuasan kasir ≥ 4,0 dari 5,0 |
| | QU-04 | Menolak stok kosong, diskon di bawah HPP, penghapusan tanpa otorisasi |
| | QU-05 | Bekerja pada skenario antrean panjang, offline, retur tanpa struk (manual lookup) |

### 7.4 Segregation of Duties (10 aturan)

| ID | Aturan |
|---|---|
| SoD-01 | Pembuat SO tidak boleh menyetujui SO yang sama (minimal 2 role) |
| SoD-02 | Pembuat SO tidak boleh membuat DO untuk SO yang sama |
| SoD-03 | Pembuat invoice tidak boleh mencatat cash receipt |
| SoD-04 | Pembuat transaksi tidak boleh void/refund transaksinya sendiri; hanya Supervisor/Manager |
| SoD-05 | Penginput harga tidak boleh memberi diskon di atas batas wewenang tanpa approval |
| SoD-06 | Pembuat master pelanggan tidak boleh menyetujui limit kredit pelanggan itu |
| SoD-07 | Pembuat transaksi penjualan tidak boleh mempost jurnal ke general ledger |
| SoD-08 | Kasir hanya melihat transaksinya sendiri; user laporan agregat tidak boleh mengedit |
| SoD-09 | Pembuat user tidak boleh menyetujui akses user tersebut (4 mata prinsip) |
| SoD-10 | User konfigurasi sistem (diskon global, tax rate) harus terpisah dari user operasional; perubahan tercatat di audit trail |

Implementasi SoD-01 s.d. SoD-05 pada kode terlihat sudah ada secara tidak langsung melalui pemisahan `userGroup` per step (`o_seller` membuat, `o_seller_spv` menyetujui, `o_gudang` memproses, `o_finance`@faktur). SoD-07 (larangan mempost jurnal) tidak tampak dikunci karena jurnal dibentuk otomatis oleh `ComJurnal` di dalam controller penjualan sendiri.

### 7.5 Compliance & Kepatuhan Regulasi (15 aturan)

| ID | Aturan | Regulasi |
|---|---|---|
| CMP-01 | Audit trail minimal 5 tahun, tidak bisa dihapus bahkan oleh admin DB | SOX, ISO 27001, PDP Law |
| CMP-02 | Timestamp dari server, timezone konsisten (UTC+7 atau UTC), NTP aktif | SOX, PCI-DSS |
| CMP-03 | Data transaksi minimal 5 tahun; data pelanggan mengikuti GDPR/PDP | UU KUP, GDPR, PDP Law |
| CMP-04 | Data production tidak boleh dipakai di dev/testing; wajib di-anonymize | SOX, ISO 27001 |
| CMP-05 | Setiap deployment tercatat (siapa, kapan, apa, nomor ticket), approval minimal 2 orang | SOX, ISO 27001 |
| CMP-06 | Backup harian, RPO ≤ 24 jam, RTO ≤ 4 jam, uji recovery tiap 3 bulan | ISO 27001, PDP Law |
| CMP-07 | Enkripsi at rest untuk data sensitif; TLS 1.2+ | PDP Law, PCI-DSS |
| CMP-08 | Password min 8 karakter (huruf besar/kecil/angka/simbol), masa berlaku maks 90 hari, cegah 5 password terakhir, lockout 5 percobaan/10 menit | ISO 27001, SOX |
| CMP-09 | MFA wajib untuk admin/superadmin, akses DB langsung, remote access | SOX, ISO 27001 |
| CMP-10 | Data kompatibel e-Faktur (PKP), faktur pajak digital valid dan tidak bisa dipalsukan | UU PPN |
| CMP-11 | Consent pelanggan, right to be forgotten, pemberitahuan kebocoran maks 72 jam | UU PDP No.27/2022 |
| CMP-12 | Deteksi fraud: void/refund di luar jam wajar, transaksi kecil berulang, akses dari IP/perangkat berbeda | Fraud Management Framework |
| CMP-13 | Integrasi pihak ketiga wajib API terautentikasi dan di-log | ISO 27001 |
| CMP-14 | Incident response: deteksi, eskalasi otomatis, log khusus | PDP Law, ISO 27001 |
| CMP-15 | Penetration test minimal tiap 6 bulan atau setelah perubahan besar | ISO 27001, PCI-DSS |

### 7.6 Lean Compliance Baseline (LC-01 … LC-12)

| ID | Kontrol | Standar | PIC | Frekuensi | Status |
|---|---|---|---|---|---|
| LC-01 | Risk register keamanan informasi modul penjualan | ISO/IEC 27001:2022 | IT Manager / SA | Triwulanan | MANDATORY |
| LC-02 | Incident response SOP 1 halaman | ISO/IEC 27001:2022 | IT Manager | Semesteran | MANDATORY |
| LC-03 | Shared responsibility matrix (cloud vs internal) | ISO/IEC 27017 | Infra/DevOps | Tahunan | MANDATORY (jika cloud) |
| LC-04 | IAM admin: MFA + least privilege | ISO/IEC 27017 | Infra/DevOps | Bulanan | MANDATORY (jika cloud) |
| LC-05 | Inventaris PII, tujuan, retensi & penghapusan | ISO/IEC 27018 | SA / DPO internal | Semesteran | MANDATORY (jika ada PII) |
| LC-06 | Hak subjek data & notifikasi insiden PII | ISO/IEC 27018 | SA / Legal | Tahunan | MANDATORY (jika ada PII) |
| LC-07 | CAPA log (temuan, akar masalah, aksi, due date, PIC, status) | ISO 9001 | QA Lead | Bulanan | MANDATORY |
| LC-08 | KPI mutu proses: defect leakage, lead time fix, SLA support | ISO 9001 | QA Lead / Support Lead | Bulanan | MANDATORY |
| LC-09 | BIA ringkas + target RTO/RPO | ISO 22301 | IT Manager / Business Owner | Tahunan | MANDATORY |
| LC-10 | Simulasi pemulihan/DR minimal 1× per tahun + lesson learned | ISO 22301 | Infra/DevOps + QA | Tahunan | MANDATORY |
| LC-11 | Register use case AI + klasifikasi risiko + human oversight | ISO/IEC 42001:2023 | Product Owner / AI Owner | Semesteran | CONDITIONAL |
| LC-12 | Monitoring performa/bias AI + rollback plan | ISO/IEC 42001:2023 | AI Owner / QA | Bulanan | CONDITIONAL |

Definisi status: `L` (Lulus), `G` (Gap), `N/A` (Not Applicable, wajib ada alasan tertulis + approval). Release dinyatakan LAYAK bila semua kontrol MANDATORY berstatus `L`; satu kontrol MANDATORY `G` → status otomatis `HOLD`. Evidence disimpan di `docs/evidence/sales/<control_id>_<topik>_<yyyy-mm-dd>.md`; rekap gate di `docs/evidence/sales/release_compliance_gate_<release-tag>_<tanggal>.md`. Penilaian tabel gate maksimal H-2 sebelum release.

### 7.7 Release Checklist (20 butir wajib)

FS-01, FS-02, RL-03, PE-01, US-01, US-03, SC-01, SC-03, CP-01, PRC-01, PRC-06, CL-01, INV-01, INV-03, OPS-01, OPS-05, DPS-01, DPS-02, SoD-01, CMP-01. Satu kriteria tidak terpenuhi → rilis DITOLAK.

### 7.8 Target KPI Setelah Go-Live

| Metrik | Target |
|---|---|
| CSAT kasir | ≥ 4,2 / 5,0 |
| Rata-rata waktu transaksi | ≤ 60 detik |
| Void/refund rate | ≤ 2% dari total transaksi |
| Stock-out karena over-allocation | 0 kejadian per bulan |
| Discount leakage | ≤ 1% dari total nilai diskon |
| SoD violation false negative | 0 kejadian per bulan |

---

## 8. Standarisasi Mekanisme Transaksi (Arah Target)

`blueprint_storage_procedure.md` menetapkan target arsitektur yang berbeda dari implementasi saat ini:

**Masalah yang diklaim.** Insert langsung dari aplikasi kasir berisiko: (1) data pincang — koneksi putus di tengah input menyebabkan stok terpotong namun jurnal COA belum terbentuk; (2) hak akses DB sisi kasir terlalu bebas, rawan manipulasi piutang atau harga modal. Metode ini dinyatakan dilarang pada skala supermarket puluhan ribu item.

**Target.** Seluruh proses transaksi dibungkus Stored Procedure dan dikunci Database Transaction sesuai ISO/IEC 9075 dan prinsip ACID (Atomicity, Consistency, Isolation, Durability). Aplikasi kasir hanya memanggil satu fungsi tunggal; DB mengeksekusi seluruh tabel terkait serentak; kegagalan satu tabel membatalkan seluruh rangkaian (*Rollback*).

**Prosedur yang diwajibkan:**

```sql
CALL sp_ProsesTransaksiPenjualan('SKU-001021', 2, 'CUST-0089', 'PIUTANG');
```

Parameter stored procedure: `p_sku_barang VARCHAR(20)`, `p_jumlah_beli INT`, `p_id_pelanggan VARCHAR(20)`, `p_metode_bayar VARCHAR(10)` ('TUNAI' atau 'PIUTANG'). Variabel internal: `v_harga_jual DECIMAL(15,2)`, `v_total_bayar DECIMAL(15,2)`, `v_stok_sekarang INT`, `v_no_nota VARCHAR(20)`, `v_akun_piutang VARCHAR(15)`. Nomor nota dibentuk `INV-<YYYYMMDD>-<4 digit>`.

Contoh pemanggilan dari sisi backend (Node.js/Express):

```javascript
app.post('/api/transaksi', async (req, res) => {
    const { sku, jumlah, id_pelanggan, metode_bayar } = req.body;
    try {
        const [result] = await db.query(
            'CALL sp_ProsesTransaksiPenjualan(?, ?, ?, ?)',
            [sku, jumlah, id_pelanggan, metode_bayar]
        );
        res.status(200).json({ success: true, data: result[0] });
    } catch (error) {
        res.status(500).json({ success: false, message: error.message });
    }
});
```

**Syarat uji QA:** (1) uji stok kosong — jumlah beli melebihi stok harus ditolak di awal tanpa baris baru di tabel nota maupun akuntansi; (2) uji interupsi jaringan — matikan koneksi tepat setelah langkah "Langkah B (Detail)" dieksekusi, dan saat database dinyalakan kembali stok tidak boleh terpotong serta jurnal tidak boleh bocor.

---

## 9. Temuan Lintas-Dokumen & Kontradiksi

### 9.1 Kontradiksi Mekanisme Transaksi

| Aspek | `blueprint_storage_procedure.md` | Implementasi aktual (`blueprint_penjualan.md`, `review-risiko`) |
|---|---|---|
| Mekanisme | Stored Procedure `sp_ProsesTransaksiPenjualan` + Database Transaction | Insert langsung ke tabel `transaksi*` dengan `$this->db->trans_start()`/`trans_complete()` |
| Nama tabel | `t_master_barang`, `t_penjualan_nota` | `transaksi`, `transaksi_values`, `transaksi_data`, `transaksi_data_values`, `transaksi_sign`, `transaksi_extstep`, `transaksi_registry` |
| Skema | Satu item per panggilan | Multi-item, multi-header, multi-step approval |

Penilaian: dokumen stored procedure adalah **dokumen target arsitektur** (tidak menyebut workspace, tanggal analisis, maupun kode aplikasi), sedangkan blueprint modul adalah dokumentasi keadaan aktual dengan path konkrit `c:/xampp/htdocs/new_san_variant` dan tanggal 2026-07-29. Untuk keputusan implementasi, kondisi aktual lebih relevan; Stored Procedure tetap layak dipertahankan sebagai target jangka panjang karena menjawab masalah data pincang yang juga dikeluhkan risk register.

### 9.2 Kontradiksi Retur Penjualan vs Pembatalan

`blueprint_penjualan.md` mendaftarkan `982` (Retur Penjualan dengan pemulihan stok) dan `1982` (Retur Penjualan Non-Stok) sebagai `jenisTr` milik modul `penjualan`, dengan `doFollowup()` releasing locker dalam mode `+active` untuk retur. Sementara `blueprint_pembatalan.md` menangani `9911`/`9912` sebagai pembatalan/reversal melalui `ComTransaksi_jurnal_revert::pair()` dan penandaan `transaksi_registry` sebagai `REVERTED`/`CANCELED`.

Penilaian: keduanya tidak sepenuhnya bertentangan — `982` adalah pengembalian barang oleh pelanggan (retur komersial), `9911`/`9912` adalah reversal dokumen secara akuntansi. Namun **sumber tidak menjelaskan batas tanggung jawabnya**: apakah retur `982` tetap melewati step approval yang sama, dan bagaimana interaksinya dengan `9749` (write-off piutang). Keterbatasan penulisan `review-modul-penjualan.md` (query `jenisTr` in `582`, `982`, `382`, dll) menunjukkan `982` memang diproses di `FollowUp.php` modul `penjualan`.

### 9.3 Kontradiksi `coTransaksiValues.php`

`blueprint_penjualan.md`, `blueprint_pembatalan.md`, `blueprint_penjualanproject.md`, dan `blueprint_saham.md` semuanya memuat `coTransaksiValues.php` sebagai config yang di-load secara normal oleh `Modul_Controller::__construct()`. `review-modul-penjualan.md` (9 Mei 2026) menyatakan file ini **duplikat** dari `coTransaksiCore.php` untuk kunci `"582"` dengan ~300 baris identik dan masuk daftar P0 untuk dihapus.

Penilaian: dokumen blueprint bertanggal 2026-07-29 (lebih baru) tetap memuat config tersebut, sementara review bertanggal 2026-05-09 sudah menandainya duplikat dan menyarankan penghapusan. Dua kemungkinan: blueprint hanya menyalin pola base controller tanpa mendeteksi duplikasi, atau duplikasi memang belum dihapus. Rekomendasi P0 pada review belum dinyatakan selesai di dokumen mana pun.

### 9.4 Perbedaan Angka PPN (10% vs 11%)

| Sumber | Angka | Rasio PPN |
|---|---|---|
| `blueprint_penjualan.md` payload contoh | `total_dpp = 3022727.0`, `total_ppn = 302273.0`, `total_grand = 3325000.0` | 10% |
| `transkrip_uat_penjualan_domestik.md` | DPP Rp 3.633.182, PPN Rp 399.650, total Rp 4.032.832 | 11% |

Penilaian: dokumen UAT (18 September 2026) paling mutakhir dan mencantumkan label eksplisit "PPN Keluaran (11%)". Payload blueprint tampaknya memakai faktor internal (induk + anak) atau contoh generik, bukan tarif PPN final. Sumber tidak menjelaskan mekanisme PPN factor secara rinci.

### 9.5 Inkonsistensi Penamaan File Controller

| Sumber | Nama File |
|---|---|
| `blueprint_penjualan.md` dan `blueprint_pembatalan.md` | Hanya menyebut controller inti: `Modul_Controller.php`, `Create.php`, `FollowUp.php`, `Transaksi.php`, `_shoppingCart.php`, `ViewDetails.php`, `Printing.php`, `ActivityReport.php`, `History.php` |
| `review-modul-penjualan.md` | Additionally menyebut `Create_total.php`, `__FollowUp.php`, `___Transaksi.php`, `CustomerApprovalApi.php`, `Rabbitmq.php`, `Debug.php`, `_construct_file.php`, `_processSelectBiaya.php` (tidak ada di tree listing) |

Blueprint 4.1 memuat struktur direktori yang bersih (tanpa file duplikat), sedangkan listing faktual pada review memuat file legacy. Blueprint adalah **gambar target** ("The Blueprint", arsitektur kode baru), bukan listing aktual.

### 9.6 `_processSelectBiaya.php` di Modul `penjualan`

Audit endpoint 2026-04-26 mencatat `application/modules/penjualan/controllers/_processSelectBiaya.php` sebagai `missing-controller` untuk endpoint `_processSelectBiaya/select`, padahal `blueprint_penjualanproject.md` (bagian 4.1) memuat `_processSelectBiaya.php` sebagai Expense Allocation Processor **milik `penjualanproject`**. Ketiga varian penjualan (`penjualan`, `penjualan_non_paket`, `penjualan_ppn`) menunjukkan temuan identik, menandakan config warisan menunjuk file yang tidak ada di masing-masing modul.

### 9.7 Inkonsistensi Konteks antara Review dan Risk Register

| Aspek | `review-modul-penjualan.md` (9 Mei 2026) | `review-risiko-modul-penjualan.md` (checkpoint 2026-06-13) |
|---|---|---|
| Fokus | Arsitektur, duplikasi, credential, testing | Mutasi session, `save()`/`doFollowup()`, idempotensi |
| Path file | `application/modules/penjualan/` (relatif) | `z:/san_sarana_staging/application/modules/penjualan/...` (workspace `san_sarana_staging`) |
| Dampak ukuran | `Create::index()` > 500 baris | `save()` line 1444 (file > 18.000 baris) |
| Status perbaikan | Rekomendasi, belum ada progress | Checkpoint 2026-06-13 dengan banyak item `[x]` |

Penilaian: risk register lebih mutakhir dan menunjukkan progres nyata pada `Create.php`; review arsitektur tidak diperbarui, sehingga butir "duplikasi masif" dan "tidak ada testing" sebaiknya diverifikasi ulang sebelum dijadikan temuan aktif.

### 9.8 Inkonsistensi Bahasa & Skala Standar

`STANDAR_MODUL_PENJUALAN_ISO25010.md` (versi dokumen 3.0) ditulis untuk konteks retail/POS: tutup kasir, shift closing, barcode, keyboard shortcut kasir, mode offline 30 menit, WCAG untukigl Accessibility. Implementasi `san` adalah ERP B2B multi-cabang dengan approval berjenjang dan integrasi CRM/subsidiary. Sumber **tidak memuat** dokumen deviasi yangStandar tersebut mewajibkan: "Pengecualian terhadap standar ini dapat diberikan oleh Product Manager, namun WAJIB didokumentasikan dalam bentuk *deviation request* tertulis yang disetujui bersama tim compliance dan security."

### 9.9 Dual-Write: Hold Dilepas atau Dikurangi?

| Sumber | Pernyataan |
|---|---|
| `workflow_penjualan.md` | Reservasi: `Active -N`, `Hold +N`; approval: stok terpotong |
| `blueprint_penjualan.md` | `hold` → `-hold` & `-active` (penjualan) atau `+active` (retur) |
| `uat_penjualan.md` | Setelah approval: `stock_locker_variant` untuk varian `hold` menjadi nol, `active` terpotong |

Tiga sumber konsisten pada intinya (hold dikurangi/dilepas, active dipotong). Perbedaan hanya pada notasi: apakah hold dihapus (`-hold`) atau di-set ke nol.

### 9.10 Kode Step `582spo` vs `5822SPO`

`review-modul-penjualan.md` menulis step suffix lowercase `582spo`; bukti UAT memakai `5822SPO.31.115.1-00021`. Perbedaan ini kemungkinan hanya konvensi penulisan (prefix `5822` + suffix step uppercase), tetapi sumber tidak menjelaskan pemetaan `5822` → `582`.

---

## 10. Lampiran: Daftar File Sumber

| Nama File | Ukuran (bytes) | Topik H1 | Peran dalam Sintesis |
|---|---|---|---|
| `blueprint_penjualan.md` | 12.748 | BLUEPRINT CETAK BIRU MODUL: PENJUALAN | Sumber utama: identitas modul, 8 `jenisTr`, peta controller/model/view, payload JSON, langkah implementasi. menjadi bagian 2, bagian 3.1–3.2, bagian 3.5–3.6, bagian 4.1–4.2, bagian 4.5 |
| `STANDAR_MODUL_PENJUALAN_ISO25010.md` | 34.968 | STANDAR KUALITAS MODUL PENJUALAN ERP (ISO/IEC 25010 + SoD + Proteksi Risiko) | Sumber standar: 9 karakteristik ISO 25010, 10 aturan SoD, 15 aturan compliance, 8 aturan harga/diskon, 6 aturan kredit, 7 aturan stok, 7 aturan kirim/tagihan, 6 aturan dropship/pre-order, 8 larangan teknis, 20 release checklist, 6 KPI, 12 kontrol lean compliance. menjadi bagian 5.4–5.8, bagian 7.3–7.8 |
| `review-modul-penjualan.md` | 21.351 | Review Modul Penjualan (`application/modules/penjualan/`) | Sumber audit kode: 5-step chain `582`, controller map, daftar file, best practice compliance, 5 persistent risks, 8 latent risks, tabel rekomendasi prioritas, root cause customer mismatch CRM→subsidiary. menjadi bagian 2.3, bagian 3.2, bagian 6.1–6.2, bagian 6.4, bagian 9.3, bagian 9.7 |
| `review-risiko-modul-penjualan.md` | 37.421 | Risk Register Modul Penjualan | Sumber risk register: 12 temuan berprioritas P0–P3 dengan nomor baris, ringkasan per file, checklist revisi A–K, helper pemecah save() yang masih direncanakan, checkpoint 2026-06-13. menjadi bagian 4.3, bagian 6.3, bagian 9.7 |
| `blueprint_storage_procedure.md` | 6.911 | DOKUMEN TEKNIS: STANDARISASI MEKANISME TRANSAKSI PENJUALAN (Stored Procedure & ACID) | Sumber target arsitektur: stored procedure `sp_ProsesTransaksiPenjualan`, parameter, contoh Express API, 2 skenario uji QA. menjadi bagian 8, bagian 9.1 |
| `blueprint_pembatalan.md` | 16.076 | BLUEPRINT CETAK BIRU MODUL: PEMBATALAN | Sumber identik: 3 `jenisTr` (`9911`, `9912`, `9749`), controller map, `ComTransaksi_jurnal_revert::pair()`, `trash_4 = 0`, payload pembatalan. menjadi bagian 2.1–2.2, bagian 3.3–3.4, bagian 5.1, bagian 9.2 |
| `blueprint_penjualanproject.md` | 6.650 | BLUEPRINT CETAK BIRU MODUL: PENJUALAN PROJECT | Sumber identik: `jenisTr` `588` dan `1784`, `_projectItemEditor.php`, `_processSelectBiaya.php`, alokasi HPP project. menjadi bagian 2.1–2.2, bagian 3.4, bagian 4.7, bagian 9.6 |
| `blueprint_saham.md` | 4.600 | BLUEPRINT CETAK BIRU MODUL: SAHAM (Share Capital & Equity Ingestion) | Sumber identik: `jenisTr` `6666` dan `757`, `_processSelectRekening.php`, pemisahan Agio/Disagio. menjadi bagian 2.1–2.2, bagian 3.4, bagian 4.8 |
| `transkrip_uat_penjualan_domestik.md` | 3.957 | Transkrip & Panduan UAT: Penjualan Sendiri / Domestik (Lokal) | Sumber bukti UAT: analisis 5W+1H, perbandingan alur SAN vs Domestik, 6 tahap rekonsiliasi dengan nomor bukti, analisis margin. menjadi bagian 2.3, bagian 7.2, bagian 9.4 |
| `uat_penjualan.md` | 874 | UAT Plan Modul Penjualan (TASK-13) | Sumber rencana uji: `LibPenjualanCreateSave`/`LibPenjualanFollowUp`, TASK-11 raw query conversion, test case manual sales→manager, expected `stock_locker_variant`. menjadi bagian 7.1, bagian 9.9 |
| `workflow_penjualan.md` | 1.065 | Alur Kerja Refactoring Modul Penjualan (TASK-12) | Sumber diagram alur: reservasi stok `Active -N` / `Hold +N`, cross-reference tabel `log`, ekstraksi library. menjadi bagian 3.1, bagian 3.6, bagian 9.9 |
| `AUDIT_TRANSAKSI_ENDPOINTS.md` | 17.494 | Audit Transaksi Endpoints (Generated 2026-04-26) | Sumber audit endpoint lintas 52 modul: ringkasan `selectorCaller`/`selectorProcessor`/`printLocation`/`receiptTemplate`, detail temuan lokal untuk 6 varian penjualan. menjadi bagian 6.5, bagian 9.6 |

**Total sumber: 12 file, 2.695 baris markdownbagian  ± 164.487 byte.**

---

*Disusun sebagai dokumen sintesis dari 12 file sumber pada direktori `W:\_md_result\file_md_san\`. Nilai kredensial yang terdapat pada `review-modul-penjualan.md` sengaja tidak direproduksi.*