224 lines
6.6 KiB
Dart
224 lines
6.6 KiB
Dart
import 'package:esc_pos_utils_plus/esc_pos_utils_plus.dart';
|
|
import 'package:print_bluetooth_thermal/print_bluetooth_thermal.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
import '../models/sale.dart';
|
|
import '../utils/format.dart';
|
|
|
|
class PrinterService {
|
|
static const String _printerKey = 'selected_printer_mac';
|
|
|
|
String? _connectedMac;
|
|
bool _isConnected = false;
|
|
|
|
String? get connectedMac => _connectedMac;
|
|
bool get isConnected => _isConnected;
|
|
|
|
Future<void> init() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
_connectedMac = prefs.getString(_printerKey);
|
|
if (_connectedMac != null) {
|
|
await connect(_connectedMac!);
|
|
}
|
|
}
|
|
|
|
Future<bool> isBluetoothEnabled() async {
|
|
return PrintBluetoothThermal.bluetoothEnabled;
|
|
}
|
|
|
|
Future<List<BluetoothInfo>> getPairedDevices() async {
|
|
final list = await PrintBluetoothThermal.pairedBluetooths;
|
|
return list;
|
|
}
|
|
|
|
Future<bool> connect(String mac) async {
|
|
try {
|
|
final connected = await PrintBluetoothThermal.connect(macPrinterAddress: mac);
|
|
_isConnected = connected;
|
|
if (connected) {
|
|
_connectedMac = mac;
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setString(_printerKey, mac);
|
|
}
|
|
return connected;
|
|
} catch (_) {
|
|
_isConnected = false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Future<void> disconnect() async {
|
|
try {
|
|
await PrintBluetoothThermal.disconnect;
|
|
} catch (_) {}
|
|
_isConnected = false;
|
|
}
|
|
|
|
Future<void> forgetPrinter() async {
|
|
_connectedMac = null;
|
|
_isConnected = false;
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.remove(_printerKey);
|
|
}
|
|
|
|
Future<List<int>> _buildReceiptBytes(Sale sale, String storeName, {String? kasirName, String? whatsapp, String? notes}) async {
|
|
final profile = await CapabilityProfile.load();
|
|
final generator = Generator(PaperSize.mm58, profile);
|
|
final bytes = <int>[];
|
|
|
|
bytes.addAll(generator.text(
|
|
storeName,
|
|
styles: const PosStyles(
|
|
align: PosAlign.center,
|
|
bold: true,
|
|
height: PosTextSize.size2,
|
|
),
|
|
));
|
|
bytes.addAll(generator.text(
|
|
formatTanggal(sale.date),
|
|
styles: const PosStyles(align: PosAlign.center),
|
|
));
|
|
bytes.addAll(generator.text(
|
|
formatWaktu(sale.date),
|
|
styles: const PosStyles(align: PosAlign.center),
|
|
));
|
|
bytes.addAll(generator.text(
|
|
'#${sale.id.toUpperCase()}',
|
|
styles: const PosStyles(align: PosAlign.center),
|
|
));
|
|
bytes.addAll(generator.hr());
|
|
|
|
for (final it in sale.items) {
|
|
bytes.addAll(generator.text('${it.name} x${it.qty}',
|
|
styles: const PosStyles(bold: true)));
|
|
bytes.addAll(generator.row([
|
|
PosColumn(
|
|
text: formatRupiah(it.price),
|
|
width: 6,
|
|
),
|
|
PosColumn(
|
|
text: formatRupiah(it.subtotal),
|
|
width: 6,
|
|
styles: const PosStyles(align: PosAlign.right),
|
|
),
|
|
]));
|
|
}
|
|
|
|
bytes.addAll(generator.hr());
|
|
if (sale.discount > 0) {
|
|
bytes.addAll(generator.row([
|
|
PosColumn(text: 'Diskon', width: 6),
|
|
PosColumn(
|
|
text: '-${formatRupiah(sale.discount)}',
|
|
width: 6,
|
|
styles: const PosStyles(align: PosAlign.right),
|
|
),
|
|
]));
|
|
}
|
|
bytes.addAll(generator.row([
|
|
PosColumn(
|
|
text: 'TOTAL',
|
|
width: 6,
|
|
styles: const PosStyles(bold: true),
|
|
),
|
|
PosColumn(
|
|
text: formatRupiah(sale.total),
|
|
width: 6,
|
|
styles: const PosStyles(bold: true, align: PosAlign.right),
|
|
),
|
|
]));
|
|
final methodLabel = sale.paymentMethod == 'transfer'
|
|
? 'Bank'
|
|
: sale.paymentMethod == 'qris'
|
|
? 'QRIS'
|
|
: sale.paymentMethod == 'split'
|
|
? 'Split Payment'
|
|
: 'Tunai';
|
|
bytes.addAll(generator.row([
|
|
PosColumn(text: 'Metode', width: 6),
|
|
PosColumn(
|
|
text: methodLabel,
|
|
width: 6,
|
|
styles: const PosStyles(align: PosAlign.right),
|
|
),
|
|
]));
|
|
if (sale.paymentMethod == 'tunai') {
|
|
bytes.addAll(generator.row([
|
|
PosColumn(text: 'Bayar', width: 6),
|
|
PosColumn(
|
|
text: formatRupiah(sale.paid),
|
|
width: 6,
|
|
styles: const PosStyles(align: PosAlign.right),
|
|
),
|
|
]));
|
|
bytes.addAll(generator.row([
|
|
PosColumn(text: 'Kembali', width: 6),
|
|
PosColumn(
|
|
text: formatRupiah(sale.change),
|
|
width: 6,
|
|
styles: const PosStyles(align: PosAlign.right),
|
|
),
|
|
]));
|
|
}
|
|
if (sale.splitPayment != null && sale.splitPayment!.isNotEmpty) {
|
|
for (final entry in sale.splitPayment!.entries) {
|
|
final label = entry.key == 'tunai'
|
|
? 'Tunai'
|
|
: entry.key == 'transfer'
|
|
? 'Transfer'
|
|
: 'QRIS';
|
|
bytes.addAll(generator.row([
|
|
PosColumn(text: 'Split $label', width: 6),
|
|
PosColumn(
|
|
text: formatRupiah(entry.value),
|
|
width: 6,
|
|
styles: const PosStyles(align: PosAlign.right),
|
|
),
|
|
]));
|
|
}
|
|
}
|
|
bytes.addAll(generator.hr());
|
|
if (kasirName != null && kasirName.isNotEmpty) {
|
|
bytes.addAll(generator.text('Kasir: $kasirName',
|
|
styles: const PosStyles(align: PosAlign.center)));
|
|
}
|
|
if (whatsapp != null && whatsapp.isNotEmpty) {
|
|
bytes.addAll(generator.text('WA: $whatsapp',
|
|
styles: const PosStyles(align: PosAlign.center)));
|
|
}
|
|
if (notes != null && notes.trim().isNotEmpty) {
|
|
final noteLines = notes.trim().split('\n');
|
|
for (final line in noteLines) {
|
|
final text = line.trim();
|
|
if (text.isEmpty) continue;
|
|
bytes.addAll(generator.text(text,
|
|
styles: const PosStyles(align: PosAlign.center)));
|
|
}
|
|
}
|
|
bytes.addAll(generator.hr());
|
|
bytes.addAll(generator.text(
|
|
'Terima kasih!',
|
|
styles: const PosStyles(align: PosAlign.center),
|
|
));
|
|
bytes.addAll(generator.feed(2));
|
|
bytes.addAll(generator.cut());
|
|
return bytes;
|
|
}
|
|
|
|
Future<String?> printSale(Sale sale, String storeName, {String? kasirName, String? whatsapp, String? notes}) async {
|
|
if (!_isConnected || _connectedMac == null) {
|
|
return 'Printer belum terhubung';
|
|
}
|
|
try {
|
|
final bytes = await _buildReceiptBytes(sale, storeName, kasirName: kasirName, whatsapp: whatsapp, notes: notes);
|
|
final result = await PrintBluetoothThermal.writeBytes(bytes);
|
|
if (result) return null;
|
|
return 'Gagal mengirim ke printer';
|
|
} catch (e) {
|
|
return e.toString();
|
|
}
|
|
}
|
|
}
|
|
|
|
final printerService = PrinterService();
|