Skip to main content
BlogERP

Hubungkan POS ERP ke Printer Bluetooth dengan Flutter

Menyambungkan POS berbasis ERP ke printer Bluetooth 58 mm di Flutter: outbox offline, cetak ulang idempoten, abstraksi port printer, dan rollout lapangan.

Matthews Wong

Software Engineer

Agustus 202612 menit baca
Hubungkan POS ERP ke Printer Bluetooth dengan Flutter

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.

Tablet yang dipasang sebagai terminal point-of-sale, dengan printer struk kecil dan laci uang di meja di bawahnya
Rantai perangkat keras yang dibahas artikel ini: tablet yang menjalankan transaksi, printer struk di sebelahnya, dan laci uang di bawahnya. Tiga perangkat yang harus sepakat tentang apa yang terjadi, melalui tautan yang tidak pernah memastikan apa pun. Foto: Travelarz / Wikimedia Commons, CC BY-SA 3.0

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.

Deretan server di dalam pusat data
Ujung yang lain dari tautan ini. ERP adalah sistem pencatat, tetapi jaraknya menit atau jam dari meja kasir lewat koneksi seluler — dan justru itulah sebabnya struk harus bisa dicetak sebelum pernah dilihat oleh mesin-mesin ini. Foto: Carl Lender from Sunrise, USA / Wikimedia Commons, CC BY 2.0

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.

  1. Rilis dengan layar pengaturan printer yang bisa mencari, memilih, menguji, dan melupakan printer, sehingga pemasangan tidak membutuhkan developer.
  2. 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.
  3. 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.
  4. Tulis runbook satu halaman untuk toko: printer tidak mau tersambung, printer mencetak sampah, struk tidak keluar. Tiga judul, tiga jawaban.
  5. 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.

FAQ

Pertanyaan Umum

Jawaban singkat untuk pertanyaan yang paling sering diajukan pembaca tentang topik ini.

Bagaimana mencegah POS mencetak struk ganda?

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.

Id penjualan sebaiknya dibuat oleh ERP atau oleh perangkat?

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.

Apakah penulisan Bluetooth yang sukses berarti struk tercetak?

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.

Apa yang seharusnya dimiliki ERP dan apa yang dimiliki perangkat?

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.

Bagaimana menjaga penggelaran di lapangan tidak berantakan?

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.