cashumit/lib/services/esc_pos_print_service.dart

247 lines
8.2 KiB
Dart

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<List<int>> generateEscPosBytes({
required List<ReceiptItem> 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<int> 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<String> 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;
}
}