Hubungkan POS ERP ke Printer Bluetooth dengan Flutter

Beri tabel print job lokal batasan unik pada pasangan id penjualan dan indeks salinan. Aslinya adalah salinan nol dan setiap cetak ulang adalah salinan bernomor yang eksplisit, sehingga kasir yang menekan cetak empat kali menghasilkan satu asli dan tiga cetak ulang bertanda jelas, bukan empat struk yang tidak bisa dibedakan.
Oleh perangkat. Pengenal yang dihasilkan lokal memberi kunci stabil yang dipakai bersama oleh print job, outbox sinkronisasi, dan dokumen ERP nantinya, sehingga percobaan ulang di lapisan mana pun merujuk ke peristiwa dunia nyata yang sama. ERP mengadopsi pengenal itu, bukan menggantinya.
Tidak. Soket memastikan byte berpindah, bukan bahwa kertas bergerak — printer bisa saja tidur, kehabisan kertas, atau macet. Kalau proses Anda bergantung pada pelanggan memegang kertas, konfirmasinya harus datang dari manusia, misalnya kasir menekan tombol konfirmasi sebelum transaksi berikutnya bisa dimulai.
ERP memiliki makna: harga, aturan pajak, data master produk, dan buku besar definitif. Perangkat memiliki peristiwa dan fakta fisik: bahwa penjualan terjadi pada suatu momen, struk mana yang tercetak, berapa salinan, dan apakah laci terbuka. Rekonsiliasi berjalan di latar belakang, tidak pernah sebagai langkah checkout.
Rilis layar pengaturan printer yang bisa mencari, menguji, dan melupakan printer sehingga tidak butuh developer di lokasi, uji coba di satu toko selama seminggu penuh termasuk akhir pekan, dan pantau dua penghitung setiap hari — penjualan belum tersinkron dan job belum tercetak — dengan memperlakukan nilai tutup bukan nol sebagai bug. Lalu tambahkan runbook satu halaman untuk toko.

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.
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.

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.
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.
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.
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.

Begitu perangkat bisa berjualan dan mencetak offline, Anda butuh cerita untuk keesokan harinya. Tiga kebiasaan membuatnya tetap terkendali.
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.
Desain teknis baru separuh pekerjaan. Urutan ini membuat pemasangan berjalan tanpa drama.
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.