import 'package:flutter/services.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:intl/intl.dart'; import 'package:cashumit/models/receipt_item.dart'; import 'package:flutter_esc_pos_utils/flutter_esc_pos_utils.dart'; import 'package:image/image.dart' as img; import 'dart:io'; /// Service untuk menghasilkan perintah ESC/POS menggunakan flutter_esc_pos_utils class EscPosPrintService { /// Menghasilkan struk dalam format byte array berdasarkan data transaksi static Future> generateEscPosBytes({ required List items, required DateTime transactionDate, required String cashierId, required String transactionId, }) async { print('Memulai generateEscPosBytes...'); print('Jumlah item: ${items.length}'); print('Tanggal transaksi: $transactionDate'); print('ID kasir: $cashierId'); print('ID transaksi: $transactionId'); // Load store info from shared preferences final prefs = await SharedPreferences.getInstance(); final storeName = prefs.getString('store_name') ?? 'TOKO SEMBAKO MURAH'; final storeAddress = prefs.getString('store_address') ?? 'Jl. Merdeka No. 123'; final adminName = prefs.getString('admin_name') ?? 'Budi Santoso'; final adminPhone = prefs.getString('admin_phone') ?? '08123456789'; final logoPath = prefs.getString('store_logo_path'); // Load logo path print('Nama toko: $storeName'); print('Alamat toko: $storeAddress'); print('Nama admin: $adminName'); print('Telepon admin: $adminPhone'); print('Logo path: $logoPath'); // Format tanggal final dateFormatter = DateFormat('dd/MM/yyyy HH:mm'); final formattedDate = dateFormatter.format(transactionDate); print('Tanggal yang diformat: $formattedDate'); // Format angka ke rupiah String formatRupiah(double amount) { final formatter = NumberFormat("#,##0", "id_ID"); return "Rp ${formatter.format(amount)}"; } // Load capability profile final profile = await CapabilityProfile.load(); final generator = Generator(PaperSize.mm58, profile); // Mulai dengan inisialisasi printer List bytes = []; // Tambahkan logo jika ada path-nya if (logoPath != null && logoPath.isNotEmpty) { try { // Membaca file gambar dari path lokal final file = File(logoPath); if (await file.exists()) { final Uint8List imageBytes = await file.readAsBytes(); // Decode gambar (format yang didukung: JPEG, PNG) final img.Image? image = img.decodeImage(imageBytes); if (image != null) { bytes += generator.image(image); } } else { throw Exception('File logo tidak ditemukan'); } } catch (e) { print('Gagal memuat logo: $e'); // Jika gagal memuat logo, gunakan teks sebagai fallback bytes += generator.text(storeName, styles: PosStyles( bold: true, height: PosTextSize.size1, width: PosTextSize.size1, align: PosAlign.center, )); } } else { // Jika tidak ada logo, gunakan nama toko sebagai header bytes += generator.text(storeName, styles: PosStyles( bold: true, height: PosTextSize.size1, width: PosTextSize.size1, align: PosAlign.center, )); } bytes += generator.text(storeAddress, styles: PosStyles(align: PosAlign.center)); bytes += generator.text('Admin: $adminName', styles: PosStyles(align: PosAlign.center)); bytes += generator.text('Telp: $adminPhone', styles: PosStyles(align: PosAlign.center)); bytes += generator.text('$formattedDate', styles: PosStyles(align: PosAlign.center)); bytes += generator.feed(1); // Garis pemisah bytes += generator.text('================================', styles: PosStyles(align: PosAlign.center)); // Informasi transaksi bytes += generator.text('Kasir: $cashierId', styles: PosStyles(bold: true)); bytes += generator.text('ID Transaksi: $transactionId', styles: PosStyles()); bytes += generator.feed(1); // Garis pemisah bytes += generator.text('================================', styles: PosStyles(align: PosAlign.center)); // Tabel item bytes += generator.row([ PosColumn( text: 'Item', width: 7, styles: PosStyles(bold: true, align: PosAlign.left), ), PosColumn( text: 'Total', width: 5, styles: PosStyles(bold: true, align: PosAlign.right), ), ]); // Item list print('Memulai iterasi item...'); for (int i = 0; i < items.length; i++) { var item = items[i]; print('Item $i: ${item.description}, qty: ${item.quantity}, price: ${item.price}, total: ${item.total}'); // Untuk item dengan detail kuantitas dan harga bytes += generator.row([ PosColumn( text: item.description, width: 12, styles: PosStyles(align: PosAlign.left), ), ]); bytes += generator.row([ PosColumn( text: '${item.quantity} x ${formatRupiah(item.price)}', width: 7, styles: PosStyles(align: PosAlign.left, height: PosTextSize.size1, width: PosTextSize.size1), ), PosColumn( text: formatRupiah(item.total), width: 5, styles: PosStyles(align: PosAlign.right), ), ]); } print('Selesai iterasi item'); // Garis pemisah sebelum total bytes += generator.text('--------------------------------', styles: PosStyles(align: PosAlign.center)); // Total final totalAmount = items.fold(0.0, (sum, item) => sum + item.total); print('Total amount: $totalAmount'); bytes += generator.row([ PosColumn( text: 'TOTAL', width: 7, styles: PosStyles(bold: true, align: PosAlign.left), ), PosColumn( text: formatRupiah(totalAmount), width: 5, styles: PosStyles(bold: true, align: PosAlign.right), ), ]); // Garis pemisah setelah total bytes += generator.text('================================', styles: PosStyles(align: PosAlign.center)); // Memuat teks kustom dari shared preferences final customDisclaimer = prefs.getString('store_disclaimer_text') ?? 'Barang yang sudah dibeli tidak dapat dikembalikan/ditukar. ' 'Harap periksa kembali struk belanja Anda sebelum meninggalkan toko.'; final customThankYou = prefs.getString('thank_you_text') ?? '*** TERIMA KASIH ***'; final customPantun = prefs.getString('pantun_text') ?? 'Belanja di toko kami, hemat dan nyaman,\n' 'Dengan penuh semangat, kami siap melayani,\n' 'Harapan kami, Anda selalu puas,\n' 'Sampai jumpa lagi, selamat tinggal.'; // Menambahkan disclaimer bytes += generator.feed(1); // Memecah disclaimer menjadi beberapa baris jika terlalu panjang final disclaimerLines = customDisclaimer.split(' '); final List wrappedDisclaimer = []; String currentLine = ''; for (final word in disclaimerLines) { if ((currentLine + word).length > 32) { wrappedDisclaimer.add(currentLine.trim()); currentLine = word + ' '; } else { currentLine += word + ' '; } } if (currentLine.trim().isNotEmpty) { wrappedDisclaimer.add(currentLine.trim()); } for (final line in wrappedDisclaimer) { bytes += generator.text(line, styles: PosStyles(align: PosAlign.center)); } // Menambahkan ucapan terima kasih bytes += generator.feed(1); bytes += generator.text(customThankYou, styles: PosStyles(align: PosAlign.center, bold: true)); // Menambahkan pantun bytes += generator.feed(1); final pantunLines = customPantun.split('\n'); for (final line in pantunLines) { bytes += generator.text(line, styles: PosStyles(align: PosAlign.center)); } // Spasi di akhir dan pemotongan kertas bytes += generator.feed(2); bytes += generator.cut(); print('Jumlah byte yang dihasilkan: ${bytes.length}'); return bytes; } }