# PHASE X4.11-I1 — RECEIPT PRINT MVP

Date: 2026-10-07

## 1. Initial audit findings

Sesuai laporan X4.11: infrastruktur print (print_jobs + destination RECEIPT +
PRINTER_KASIR→RECEIPT + claim/lease/ACK) sudah ada dan produksi; yang
hilang hanya pemicu job receipt di jalur payment success + builder payload
teks. Tidak ada discrepancy dari laporan audit.

## 2. Files changed

| File | Change |
|---|---|
| `app/Modules/Payment/PaymentController.php` | Enqueue ONE RECEIPT job setelah payment commit + order COMPLETED (try/catch — kegagalan di-audit `receipt_print.create_failed`, payment TIDAK pernah rollback) |
| `app/Modules/Receipt/ReceiptService.php` | +`buildPrintPayload(array $receipt): string` — teks struk dari snapshot receipt view model |
| `tests/integration/ReceiptPrintJobTest.php` | NEW — 7 targeted tests |
| `reports/PHASE_X4_11_I1_RECEIPT_PRINT_MVP.md` | Report ini |

## 3. Exact implementation

- **Payload:** plain text `\n` (konvensi kitchen/voucher): business_name,
  "STRUK PEMBAYARAN", CABANG, No order, Waktu completed_at, Kasir, item
  (qty× nama + line total + catatan), Subtotal, Diskon Event (child lines),
  Diskon Poin, TOTAL, metode + Ref, Tunai diterima/Kembalian, Refund,
  footer setting. **Semua nilai dari `ReceiptService::build` (snapshot) —
  tanpa baca master produk, tanpa kalkulasi ulang.**
- **Job:** `PrintJobService::create(branch, 'RECEIPT', payload, orderId)`
  — reuse persis jalur KITCHEN/voucher; `order_id` = referensi transaksi.

## 4. Payment transaction semantics

Payment transaction (`PaymentService::record`) TIDAK diubah. Job receipt
dibuat **SETELAH** `record()` sukses (commit) dan hanya bila order
berstatus COMPLETED — persis di titik keputusan redirect SIMPAN→STRUK.
Partial payment tidak membuat job (receipt hanya untuk COMPLETED).

## 5. Receipt payload design

Model B (immutable transaction snapshot) — payload adalah proyeksi teks
dari view model yang sama dengan struk HTML; disimpan di `print_jobs.payload`.

## 6. Print job creation point

`PaymentController::record` (sekali per payment sukses penuh) — BUKAN di
`GET /receipts/{id}`, BUKAN di `window.print()`, BUKAN di refresh/AJAX.

## 7. Duplicate protection

- Submit ganda: server menolak (amount > remaining) → job kedua mustahil
  (TEST 6). Refresh halaman receipt: read-only → nol job (TEST 7).

## 8. Error semantics

- PAYMENT FAILURE (DomainException) → tidak ada job (TEST 4).
- PAYMENT SUCCESS + enqueue gagal (infrastruktur) → payment tetap SUCCESS,
  audit `receipt_print.create_failed`, flash note — tidak swallow diam-diam.

## 9–10. Tests + results

`php tests/run.php ReceiptPrintJobTest` → **7/7 PASS**:
TEST 1 (job RECEIPT + branch + order_id + PENDING), TEST 2 (payload
authoritative), TEST 3 (snapshot immutable vs master mutation + restore
fixture), TEST 4 (failed payment → no job), TEST 5 (payment sukses tanpa
printer RECEIPT — printer-independent), TEST 6 (repeated submit → 1 job),
TEST 7 (receipt page tidak menambah job).
Full regression: `php tests/run.php` → **623/623 PASS, 0 FAILED**
(kitchen/payment/UX regresi hijau — TEST 8).

## 11. Acceptance matrix

| # | Criterion | Evidence | Status |
|---|---|---|---|
| 1 | Payment success preserved | full regression + TEST 1/5 | PASS |
| 2 | Exactly one original receipt job | TEST 1/6 | PASS |
| 3 | Job destination RECEIPT | TEST 1 | PASS |
| 4 | Correct branch | TEST 1 | PASS |
| 5 | Correct order reference | TEST 1 (order_id) | PASS |
| 6 | Correct receipt payload | TEST 2 | PASS |
| 7 | Snapshot semantics | TEST 3 | PASS |
| 8 | Failed payment → no job | TEST 4 | PASS |
| 9 | Printer does not block payment | TEST 5 | PASS |
| 10 | Receipt page still works | TEST 7 (200) | PASS |
| 11 | Receipt page no duplicate job | TEST 7 | PASS |
| 12 | Kitchen tests PASS | full regression | PASS |
| 13 | No DB migration | — | PASS |
| 14 | No Android changes | — | PASS |
| 15 | No Bluetooth changes | — | PASS |
| 16 | No new dependency | — | PASS |

## 12. Regression results

623/623 PASS (fresh DB setiap run; migration runner + seed + fixtures).

## 13. Remaining risks

1. Duplikat fisik pada jendela at-least-once (print-OK + ACK-lost + lease
   expiry) — sama dengan kitchen, terdokumentasi.
2. Struk tertunda (PENDING) bila agent/printer offline — payment tidak
   terpengaruh (by design).
3. Reprint thermal belum ada (fase X4.11-I2).
4. Printer kasir fisik belum disurvei (SPP/lebar kertas) — hardware phase.

## 14. Next phase recommendation

**X4.11-I2 — REPRINT + HARDWARE VALIDATION:** tombol cetak ulang eksplisit
(job baru, payload snapshot sama) + daftarkan device `PRINTER_KASIR` +
survey/pairing printer kasir + validasi fisik (H-matrix serupa X4.25).

## 15. Evidence — git

```text
branch: master
HEAD: fdda4e0 fix(print-bridge): DPAPI token loading and relative config path
working tree: M app/Modules/Payment/PaymentController.php
              M app/Modules/Receipt/ReceiptService.php
              ?? tests/integration/ReceiptPrintJobTest.php
              ?? reports/ (incl. report ini)
changed files (X4.11-I1): 2 source + 1 test + 1 report
```
