fix: perbaiki item dan total pecah baris di struk thermal
Library flutter_esc_pos_utils mengirim ESC a (align) di tengah
baris
pada generator.row(), menyebabkan kolom right-aligned tercetak di
baris baru. Ganti semua row() campuran left+right alignment
dengan
text() + _padRow() (manual space padding 32 char) agar tidak ada
perintah ESC a di tengah baris.
- Tambah helper _padRow(left, right) untuk padding 32 karakter
- Item: deskripsi 1 baris (width 12), qty×price + total 1 baris
- Subtotal, TOTAL, BAYAR, KEMBALI/TIP, KURANG: text() + _padRow()
This commit is contained in:
parent
4140af7ba0
commit
860a929da8
|
|
@ -460,15 +460,11 @@ class _ReceiptScreenState extends State<ReceiptScreen> {
|
|||
),
|
||||
),
|
||||
if (_isPrinting)
|
||||
Positioned(
|
||||
top: 50,
|
||||
right: 16,
|
||||
child: PrintingStatusCard(
|
||||
isVisible: _isPrinting,
|
||||
onDismiss: () {
|
||||
setState(() => _isPrinting = false);
|
||||
},
|
||||
),
|
||||
PrintingStatusCard(
|
||||
isVisible: _isPrinting,
|
||||
onDismiss: () {
|
||||
setState(() => _isPrinting = false);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -122,6 +122,9 @@ class EscPosPrintService {
|
|||
// Generate all receipt sections
|
||||
List<int> bytes = [];
|
||||
|
||||
bytes.addAll(generator.reset());
|
||||
bytes.addAll([0x1D, 0x21, 0x00]); // GS ! 0 = paksa ukuran karakter normal
|
||||
|
||||
// Add store logo if available (diproses di isolate terpisah)
|
||||
bytes.addAll(await _addStoreLogo(generator, storeConfig.logoPath));
|
||||
|
||||
|
|
@ -137,26 +140,17 @@ class EscPosPrintService {
|
|||
// Header grup: satu garis + nama sumber (tanpa "Dari:")
|
||||
bytes.addAll(generator.text('--------------------------------',
|
||||
styles: PosStyles(align: PosAlign.center)));
|
||||
bytes.addAll(generator.text(
|
||||
group.sourceAccountName ?? '(belum dipilih)',
|
||||
styles: PosStyles(align: PosAlign.left, bold: true)));
|
||||
bytes.addAll(generator.text(group.sourceAccountName ?? '(belum dipilih)',
|
||||
styles: PosStyles(align: PosAlign.center, bold: true)));
|
||||
|
||||
// Item list grup
|
||||
bytes.addAll(_addItemList(generator, group.items));
|
||||
|
||||
// Subtotal grup
|
||||
bytes.addAll(generator.row([
|
||||
PosColumn(
|
||||
text: 'Subtotal',
|
||||
width: 7,
|
||||
styles: PosStyles(align: PosAlign.left, bold: true),
|
||||
),
|
||||
PosColumn(
|
||||
text: _formatRupiah(group.total),
|
||||
width: 5,
|
||||
styles: PosStyles(align: PosAlign.right, bold: true),
|
||||
),
|
||||
]));
|
||||
bytes.addAll(generator.text(
|
||||
_padRow('Subtotal', _formatRupiah(group.total)),
|
||||
styles: PosStyles(align: PosAlign.left, bold: true),
|
||||
));
|
||||
|
||||
if (gi < groupsWithItems.length - 1) {
|
||||
bytes.addAll(generator.feed(1));
|
||||
|
|
@ -239,24 +233,6 @@ class EscPosPrintService {
|
|||
|
||||
bytes.addAll(generator.feed(1));
|
||||
|
||||
// Separator line
|
||||
bytes.addAll(generator.text('================================',
|
||||
styles: PosStyles(align: PosAlign.center)));
|
||||
|
||||
// Item table header
|
||||
bytes.addAll(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),
|
||||
),
|
||||
]));
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
|
|
@ -268,7 +244,6 @@ class EscPosPrintService {
|
|||
try {
|
||||
var item = items[i];
|
||||
|
||||
// Item description row
|
||||
bytes.addAll(generator.row([
|
||||
PosColumn(
|
||||
text: item.description,
|
||||
|
|
@ -276,25 +251,11 @@ class EscPosPrintService {
|
|||
styles: PosStyles(align: PosAlign.left),
|
||||
),
|
||||
]));
|
||||
|
||||
// Quantity and price row
|
||||
bytes.addAll(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),
|
||||
),
|
||||
]));
|
||||
bytes.addAll(generator.text(
|
||||
_padRow(_compactQtyPrice(item.quantity, item.price), _formatRupiahCompact(item.total)),
|
||||
styles: PosStyles(align: PosAlign.left),
|
||||
));
|
||||
} catch (e) {
|
||||
// Lanjutkan ke item berikutnya jika ada error
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
@ -303,8 +264,8 @@ class EscPosPrintService {
|
|||
}
|
||||
|
||||
/// Add totals and payment information
|
||||
static List<int> _addTotals(Generator generator, List<ReceiptGroup> groups,
|
||||
PaymentInfo? paymentInfo,
|
||||
static List<int> _addTotals(
|
||||
Generator generator, List<ReceiptGroup> groups, PaymentInfo? paymentInfo,
|
||||
{String? tipSourceAccountName}) {
|
||||
List<int> bytes = [];
|
||||
|
||||
|
|
@ -313,22 +274,13 @@ class EscPosPrintService {
|
|||
styles: PosStyles(align: PosAlign.center)));
|
||||
|
||||
// Calculate grand total
|
||||
double totalAmount =
|
||||
groups.fold(0.0, (sum, group) => sum + group.total);
|
||||
double totalAmount = groups.fold(0.0, (sum, group) => sum + group.total);
|
||||
|
||||
// Add total row
|
||||
bytes.addAll(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),
|
||||
),
|
||||
]));
|
||||
bytes.addAll(generator.text(
|
||||
_padRow('TOTAL', _formatRupiah(totalAmount)),
|
||||
styles: PosStyles(bold: true, align: PosAlign.left),
|
||||
));
|
||||
|
||||
// Add payment information if provided
|
||||
if (paymentInfo != null && paymentInfo.paymentAmount > 0) {
|
||||
|
|
@ -336,10 +288,6 @@ class EscPosPrintService {
|
|||
tipSourceAccountName: tipSourceAccountName));
|
||||
}
|
||||
|
||||
// Final separator
|
||||
bytes.addAll(generator.text('================================',
|
||||
styles: PosStyles(align: PosAlign.center)));
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
|
|
@ -351,59 +299,22 @@ class EscPosPrintService {
|
|||
final changeAmount = paymentInfo.paymentAmount - totalAmount;
|
||||
|
||||
// Payment amount row
|
||||
bytes.addAll(generator.row([
|
||||
PosColumn(
|
||||
text: 'BAYAR',
|
||||
width: 7,
|
||||
styles: PosStyles(align: PosAlign.left),
|
||||
),
|
||||
PosColumn(
|
||||
text: _formatRupiah(paymentInfo.paymentAmount),
|
||||
width: 5,
|
||||
styles: PosStyles(align: PosAlign.right),
|
||||
),
|
||||
]));
|
||||
bytes.addAll(generator.text(
|
||||
_padRow('BAYAR', _formatRupiah(paymentInfo.paymentAmount)),
|
||||
styles: PosStyles(align: PosAlign.left),
|
||||
));
|
||||
|
||||
if (changeAmount >= 0) {
|
||||
// Change amount row
|
||||
bytes.addAll(generator.row([
|
||||
PosColumn(
|
||||
text: 'KEMBALI',
|
||||
width: 7,
|
||||
styles: PosStyles(align: PosAlign.left),
|
||||
),
|
||||
PosColumn(
|
||||
text: _formatRupiah(changeAmount),
|
||||
width: 5,
|
||||
styles: PosStyles(align: PosAlign.right),
|
||||
),
|
||||
]));
|
||||
|
||||
// Tip information
|
||||
if (paymentInfo.isTip && changeAmount > 0) {
|
||||
bytes.addAll(generator.text('SEBAGAI TIP: YA',
|
||||
styles: PosStyles(align: PosAlign.center, bold: true)));
|
||||
if (tipSourceAccountName != null && tipSourceAccountName.isNotEmpty) {
|
||||
bytes.addAll(generator.text('Sumber tip: $tipSourceAccountName',
|
||||
styles: PosStyles(align: PosAlign.center)));
|
||||
}
|
||||
bytes.addAll(generator.text('(Uang kembalian sebagai tip)',
|
||||
styles: PosStyles(align: PosAlign.center)));
|
||||
}
|
||||
bytes.addAll(generator.text(
|
||||
_padRow(paymentInfo.isTip ? 'TIP' : 'KEMBALI', _formatRupiah(changeAmount)),
|
||||
styles: PosStyles(align: PosAlign.left),
|
||||
));
|
||||
} else {
|
||||
// Amount still needed row
|
||||
bytes.addAll(generator.row([
|
||||
PosColumn(
|
||||
text: 'KURANG',
|
||||
width: 7,
|
||||
styles: PosStyles(align: PosAlign.left),
|
||||
),
|
||||
PosColumn(
|
||||
text: _formatRupiah(changeAmount.abs()),
|
||||
width: 5,
|
||||
styles: PosStyles(align: PosAlign.right),
|
||||
),
|
||||
]));
|
||||
bytes.addAll(generator.text(
|
||||
_padRow('KURANG', _formatRupiah(changeAmount.abs())),
|
||||
styles: PosStyles(align: PosAlign.left),
|
||||
));
|
||||
}
|
||||
|
||||
bytes.addAll(generator.text('--------------------------------',
|
||||
|
|
@ -477,6 +388,24 @@ class EscPosPrintService {
|
|||
return "Rp ${formatter.format(amount)}";
|
||||
}
|
||||
|
||||
/// Format angka compact tanpa Rp dan separator
|
||||
static String _formatRupiahCompact(double amount) {
|
||||
return NumberFormat("0", "id_ID").format(amount);
|
||||
}
|
||||
|
||||
/// Format qty x price compact: "2x65000"
|
||||
static String _compactQtyPrice(double quantity, double price) {
|
||||
final qtyInt = quantity.toInt();
|
||||
final priceStr = NumberFormat("0", "id_ID").format(price);
|
||||
return "${qtyInt}x$priceStr";
|
||||
}
|
||||
|
||||
/// Pad left/right text into one line to avoid ESC a alignment issues in row()
|
||||
static String _padRow(String left, String right, {int maxChars = 32}) {
|
||||
final padding = maxChars - left.length - right.length;
|
||||
return left + ' ' * (padding > 0 ? padding : 1) + right;
|
||||
}
|
||||
|
||||
/// Mencetak struk ke printer thermal
|
||||
static Future<void> printToThermalPrinter({
|
||||
required List<ReceiptGroup> groups,
|
||||
|
|
|
|||
Loading…
Reference in New Issue