Ringkasan Utama
Di antara ERP dan printer struk Bluetooth terdapat dua tautan yang tidak andal: API sinkronisasi yang putus-nyambung dan koneksi Bluetooth yang tidak pernah memastikan pencetakan. Tabel print job lokal dengan pasangan unik id penjualan dan indeks salinan, ditambah antarmuka printer yang bisa dipalsukan, mengubah kedua kegagalan itu menjadi status yang terlihat dan bisa dipulihkan.
ERP tahu berapa nilai sebuah penjualan. Printer Bluetooth tahu cara membakar titik ke kertas. Semua hal menyakitkan dalam point-of-sale mobile hidup di celah antara keduanya, dan celah itu lebih lebar daripada kelihatannya: jaringan bisa hilang berjam-jam, dan printer tidak akan pernah memberi tahu bahwa pelanggan benar-benar menerima struknya.
Ini arsitektur yang saya pakai untuk menutup celah itu, dibangun di atas satu prinsip — perangkat harus bisa menyelesaikan transaksi dan mencetaknya tanpa server, lalu merekonsiliasi belakangan tanpa pernah menggandakan struk.
Dua tautan tidak andal yang tersusun seri
Menggambar sistemnya secara jujur adalah langkah pertama, karena gambar itu memperlihatkan bahwa kedua ranah kegagalan bersifat independen dan harus ditangani terpisah.
ERP (source of truth)
| sale document, tax rules, prices, stock
v
Sync API ---- intermittent, may be offline for hours
|
v
Flutter app (device)
| local sale record -> outbox -> printer job
v
Bluetooth SPP ---- 58 mm printer, no acknowledgement of "printed"
// Two independent unreliable links, in series. Every design decision
// below exists because a failure on either side must not lose a sale
// and must not print a receipt twice.Tautan ERP gagal secara lambat dan kasatmata: permintaan timeout, pekerjaan sinkronisasi menumpuk, seseorang menyadarinya. Tautan printer gagal seketika dan senyap: byte-nya sudah ditulis, soket menerimanya, dan apakah kertas bergerak tidak diketahui. Merancang satu kebijakan percobaan ulang untuk keduanya adalah cara aplikasi berakhir mencetak empat salinan satu struk.

Apa yang menjadi milik perangkat dan apa milik ERP
Pembagiannya tidak kentara, dan salah membaginya menghasilkan entah aplikasi yang tidak bisa berjualan offline atau ERP yang tidak bisa dipercaya. Aturan saya: ERP memiliki makna, perangkat memiliki peristiwa.
- ERP memiliki harga, aturan pajak, data master produk, dan buku besar definitif. Perangkat menyimpannya sebagai cache dan tidak pernah mengarangnya.
- Perangkat memiliki fakta bahwa sebuah penjualan terjadi pada suatu momen, dengan pengenal yang dihasilkan secara lokal dan kemudian diadopsi ERP alih-alih diganti.
- Perangkat memiliki segala hal fisik: struk mana yang tercetak, berapa salinan, apakah laci terbuka. Tidak satu pun dari itu pantas berada di dokumen penjualan ERP.
- Rekonsiliasi adalah pekerjaan latar belakang, bukan langkah checkout. Kasir tidak boleh menunggu server mengakui apa pun.
Menghasilkan pengenal penjualan di perangkat adalah detail yang membuat sisanya bekerja. Ia memberi Anda kunci stabil untuk print job, baris outbox, dan dokumen ERP nantinya, sehingga percobaan ulang di lapisan mana pun merujuk ke peristiwa dunia nyata yang sama.
Tabel print job, dan kenapa ia punya batasan unik
Print job hidup di penyimpanan lokal berdampingan dengan penjualannya, bukan di memori. Menyimpannya itulah yang membuat aplikasi selamat ketika dimatikan di tengah pencetakan, dan batasan unik itulah yang mencegah kasir yang gugup menghasilkan empat struk identik.
-- Local SQLite on the device. The ERP never sees this table; it is
-- the device's own memory of what physically happened.
CREATE TABLE print_job (
id TEXT PRIMARY KEY, -- uuid v4, generated on device
sale_id TEXT NOT NULL, -- the local sale it belongs to
copy_index INTEGER NOT NULL, -- 0 = original, 1..n = reprints
payload BLOB NOT NULL, -- the exact ESC/POS bytes
state TEXT NOT NULL, -- queued | printing | done | failed
attempts INTEGER NOT NULL DEFAULT 0,
last_error TEXT,
created_at TEXT NOT NULL,
UNIQUE (sale_id, copy_index)
);
-- The unique pair is the idempotency key. A tap-happy cashier pressing
-- "print" four times produces one original and three explicit reprints,
-- not four indistinguishable receipts.Memodelkan cetak ulang sebagai indeks salinan yang eksplisit alih-alih sebagai percobaan berulang juga memberi manfaat operasional: cetak ulang bisa ditandai jelas di kertas, dan itulah yang diinginkan auditor maupun pemilik toko ketika pelanggan datang membawa dua struk untuk satu transaksi.
Simpan byte yang sudah dirender di baris job, bukan sekadar referensi penjualan. Harga berubah, produk berganti nama, dan cetak ulang dua hari kemudian seharusnya mereproduksi struk sebagaimana adanya dulu, bukan sebagaimana katalog sekarang.
Perlakukan printer sebagai sebuah port
Keputusan desain yang paling terbayar adalah menolak membiarkan tipe-tipe Bluetooth bocor ke atas. Semua lapisan di atas transport berbicara ke satu antarmuka kecil, dan antarmuka itu punya tiga implementasi.
/// The printer is a port, not a feature. Everything above it — the
/// receipt builder, the queue, the ERP sync — depends on this
/// interface, so the app can be tested and demoed with no hardware.
abstract interface class ReceiptPrinter {
Future<bool> isAvailable();
Future<void> send(List<int> bytes);
}
class BluetoothReceiptPrinter implements ReceiptPrinter { /* real device */ }
class FilePrinter implements ReceiptPrinter { /* writes .bin for tests */ }
class NoopPrinter implements ReceiptPrinter { /* demo mode, no device */ }
// Swapping FilePrinter in during integration tests is what let us run
// the entire checkout flow in CI, on a machine with no Bluetooth at all.Imbalannya muncul di tiga tempat. Tes integrasi menjalankan seluruh alur checkout di CI dengan printer yang menulis ke berkas. Demo penjualan berjalan di kamar hotel tanpa perangkat keras. Dan ketika klien meminta printer meja berjaringan berdampingan dengan yang portabel, perubahannya adalah implementasi baru dari antarmuka yang sama, bukan perubahan pada alur transaksi.

Merekonsiliasi apa yang tercetak dengan apa yang diyakini ERP
Begitu perangkat bisa berjualan dan mencetak offline, Anda butuh cerita untuk keesokan harinya. Tiga kebiasaan membuatnya tetap terkendali.
- Sinkronkan penjualannya lebih dulu, metadata cetak belakangan. Catatan keuangan lebih penting daripada fakta bahwa selembar kertas telah dihasilkan.
- Sertakan pengenal penjualan lokal di dokumen ERP, agar tim dukungan bisa berpindah dari struk kertas ke catatan ERP dengan satu pencarian.
- Laporkan jumlah penjualan yang belum tersinkron dan jumlah job yang belum tercetak di dalam aplikasi, pada layar yang sama, terlihat oleh toko bukan hanya oleh Anda.
Poin terakhir mengubah cara pemasangan berjalan bagi saya. Ketika staf bisa melihat dua angka yang keduanya seharusnya nol saat tutup, mereka mengangkat masalah pada hari itu juga, bukan di akhir bulan.
Jangan pernah memperlakukan penulisan sukses ke soket Bluetooth sebagai bukti struk tercetak. Soket mengonfirmasi perpindahan data, bukan kertas. Kalau proses bisnis Anda bergantung pada pelanggan memegang kertas, konfirmasinya harus datang dari manusia — kasir menekan tombol konfirmasi, atau transaksi berikutnya diblokir sampai mereka melakukannya.
Menggelarnya ke toko sungguhan
Desain teknis baru separuh pekerjaan. Urutan ini membuat pemasangan berjalan tanpa drama.
- Rilis dengan layar pengaturan printer yang bisa mencari, memilih, menguji, dan melupakan printer, sehingga pemasangan tidak membutuhkan developer.
- Uji coba di satu toko selama seminggu penuh, termasuk akhir pekan, sebelum menjanjikan apa pun. Volume hari kerja menyembunyikan semua masalah baterai dan sambung ulang.
- Pantau dua penghitung itu — penjualan belum tersinkron dan job belum tercetak — setiap hari selama seminggu itu, dan perlakukan nilai tutup yang bukan nol sebagai bug, bukan sebagai kebisingan.
- Tulis runbook satu halaman untuk toko: printer tidak mau tersambung, printer mencetak sampah, struk tidak keluar. Tiga judul, tiga jawaban.
- Baru setelah itu gelar ke sisanya, satu toko per hari, agar interaksi buruk dengan model perangkat tertentu muncul sekali, bukan di semua tempat sekaligus.
Runbook adalah artefak dengan daya ungkit terbesar dalam daftar itu. Sebagian besar panggilan dukungan adalah soal pairing, baterai, atau kertas, dan toko yang bisa menyelesaikan itu sendiri hanya akan menelepon Anda untuk kegagalan yang benar-benar menarik.
Menghubungkan ERP ke printer Bluetooth adalah latihan menerima bahwa kedua tautannya tidak andal lalu merancang agar tak satu pun bisa menghilangkan uang atau menggandakan kertas. Tabel job lokal dengan kunci idempotensi sungguhan, antarmuka printer yang bisa dipalsukan, dan dua penghitung yang bisa dilihat toko akan menutup hampir semua yang dilemparkan penggelaran lapangan kepada Anda.

