update printer

This commit is contained in:
Efril 2026-05-25 23:33:49 +07:00
parent 6d9377f4ab
commit c45848bcf2
6 changed files with 169 additions and 151 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -44,7 +44,7 @@ class FcmService {
} }
Future<void> _setupLocalNotifications() async { Future<void> _setupLocalNotifications() async {
const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher'); const androidSettings = AndroidInitializationSettings('@drawable/ic_notification');
const iosSettings = DarwinInitializationSettings( const iosSettings = DarwinInitializationSettings(
requestAlertPermission: true, requestAlertPermission: true,
requestBadgePermission: true, requestBadgePermission: true,

View File

@ -32,8 +32,6 @@ class PrintUi {
phoneNumber: outlet.phoneNumber, phoneNumber: outlet.phoneNumber,
); );
bytes += builder.dateTime(DateTime.now());
bytes += builder.orderInfo( bytes += builder.orderInfo(
orderNumber: order.orderNumber, orderNumber: order.orderNumber,
customerName: order.metadata['customer_name'] ?? '-', customerName: order.metadata['customer_name'] ?? '-',
@ -90,34 +88,34 @@ class PrintUi {
bytes += generator.reset(); bytes += generator.reset();
// Header
bytes += builder.textCenter('Table Checker', bold: true);
bytes += builder.separator();
bytes += builder.textCenter(
'Table : ${order.tableNumber.isNotEmpty ? order.tableNumber : '-'}',
bold: true,
);
bytes += builder.separator();
// Order info label : value, left aligned
bytes += builder.orderInfoSimple(
orderNumber: order.orderNumber,
orderType: order.orderType,
cashierName: cashierName,
);
bytes += builder.separator();
// Items qty NAMA (uppercase), variant indent
for (final item in order.orderItems) { for (final item in order.orderItems) {
bytes += builder.separator(); final name = item.productName.toUpperCase();
bytes += builder.printerType(printerType: 'CHECKER'); bytes += builder.itemText('${item.quantity} $name');
bytes += builder.separator(); if (item.productVariantName.isNotEmpty) {
bytes += builder.itemText(' ${item.productVariantName.toUpperCase()}', bold: false);
bytes += builder.dateTime(DateTime.now()); }
bytes += builder.orderInfo( if (item.notes.isNotEmpty) {
orderNumber: order.orderNumber, bytes += builder.itemText(' *${item.notes}', bold: false);
customerName: order.metadata['customer_name'] ?? '-', }
cashierName: cashierName,
paymentMethod: order.payments.isEmpty
? ''
: order.payments.last.paymentMethodName,
tableNumber: order.tableNumber,
);
bytes += builder.orderType(order.orderType);
bytes += builder.emptyLines(1);
bytes += builder.orderItem(
productName: item.productName,
quantity: item.quantity,
unitPrice: item.unitPrice.currencyFormatRpV2,
totalPrice: item.totalPrice.currencyFormatRpV2,
variantName: item.productVariantName,
notes: item.notes,
);
} }
bytes += builder.separator(); bytes += builder.separator();
@ -144,47 +142,39 @@ class PrintUi {
paperSize: paper, paperSize: paper,
); );
// Group items by category
final Map<String, List<OrderItem>> groupedItems = {};
for (final item in order.orderItems) { for (final item in order.orderItems) {
final key = item.categoryName.isNotEmpty ? item.categoryName : 'Lainnya';
groupedItems.putIfAbsent(key, () => []).add(item);
}
for (final entry in groupedItems.entries) {
final categoryName = entry.key;
final items = entry.value;
bytes += generator.reset(); bytes += generator.reset();
// Header
bytes += builder.textCenter('Kitchen', bold: true);
bytes += builder.separator(); bytes += builder.separator();
bytes += builder.printerType(printerType: 'KITCHEN'); bytes += builder.textCenter(
'Table : ${order.tableNumber.isNotEmpty ? order.tableNumber : '-'}',
bold: true,
);
bytes += builder.separator(); bytes += builder.separator();
bytes += builder.dateTime(DateTime.now());
bytes += builder.orderInfo( // Order info
bytes += builder.orderInfoSimple(
orderNumber: order.orderNumber, orderNumber: order.orderNumber,
customerName: order.metadata['customer_name'] ?? '-', orderType: order.orderType,
cashierName: cashierName, cashierName: cashierName,
tableNumber: order.tableNumber,
); );
bytes += builder.orderType(categoryName); bytes += builder.separator();
bytes += builder.emptyLines(1); // Single item
final name = item.productName.toUpperCase();
for (final item in items) { bytes += builder.itemText('${item.quantity} $name');
bytes += builder.orderItem( if (item.productVariantName.isNotEmpty) {
productName: item.productName, bytes += builder.itemText(' ${item.productVariantName.toUpperCase()}', bold: false);
quantity: item.quantity, }
unitPrice: item.unitPrice.currencyFormatRpV2, if (item.notes.isNotEmpty) {
totalPrice: item.totalPrice.currencyFormatRpV2, bytes += builder.itemText(' *${item.notes}', bold: false);
variantName: item.productVariantName,
notes: item.notes,
);
} }
bytes += builder.separator(); bytes += builder.separator();
bytes += builder.footer(); bytes += builder.cutOnly();
} }
return bytes; return bytes;
@ -307,8 +297,6 @@ class PrintUi {
phoneNumber: outlet.phoneNumber, phoneNumber: outlet.phoneNumber,
); );
bytes += builder.dateTime(DateTime.now());
bytes += builder.orderInfo( bytes += builder.orderInfo(
orderNumber: order.orderNumber, orderNumber: order.orderNumber,
customerName: order.metadata['customer_name'] ?? '-', customerName: order.metadata['customer_name'] ?? '-',

View File

@ -9,7 +9,31 @@ class ReceiptComponentBuilder {
ReceiptComponentBuilder({required this.generator, this.paperSize = 58}); ReceiptComponentBuilder({required this.generator, this.paperSize = 58});
/// Print text centered with custom style /// Helper: returns size2 for 80mm paper, size1 for 58mm
PosTextSize get _titleSize =>
paperSize == 80 ? PosTextSize.size3 : PosTextSize.size1;
/// Font type per paper size easy to change here
/// 58mm fontA, 80mm fontA
PosFontType get _font =>
paperSize == 80 ? PosFontType.fontA : PosFontType.fontA;
/// Text size for body height size2 for 80mm to appear taller, size1 for 58mm
PosTextSize get _bodySize =>
paperSize == 80 ? PosTextSize.size2 : PosTextSize.size1;
/// Body width stays size1 always to prevent overflow
PosTextSize get _bodyWidth => PosTextSize.size1;
/// Characters per line based on paper size (always size1 font)
String get _separatorLine => paperSize == 80
? '------------------------------------------------'
: '--------------------------------';
// ---------------------------------------------------------------------------
// Basic text
// ---------------------------------------------------------------------------
List<int> textCenter( List<int> textCenter(
String text, { String text, {
bool bold = false, bool bold = false,
@ -23,32 +47,41 @@ class ReceiptComponentBuilder {
align: PosAlign.center, align: PosAlign.center,
height: height, height: height,
width: width, width: width,
fontType: _font,
), ),
); );
} }
/// Print text aligned left
List<int> textLeft(String text, {bool bold = false}) { List<int> textLeft(String text, {bool bold = false}) {
return generator.text( return generator.text(
text, text,
styles: PosStyles(bold: bold, align: PosAlign.left), styles: PosStyles(
bold: bold,
align: PosAlign.left,
fontType: _font,
height: _bodySize,
width: _bodyWidth,
),
); );
} }
/// Print text aligned right
List<int> textRight(String text, {bool bold = false}) { List<int> textRight(String text, {bool bold = false}) {
return generator.text( return generator.text(
text, text,
styles: PosStyles(bold: bold, align: PosAlign.right), styles: PosStyles(
bold: bold,
align: PosAlign.right,
fontType: _font,
height: _bodySize,
width: _bodyWidth,
),
); );
} }
/// Characters per line based on paper size (always size1 font) // ---------------------------------------------------------------------------
String get _separatorLine => paperSize == 80 // Layout helpers
? '------------------------------------------------' // ---------------------------------------------------------------------------
: '--------------------------------';
/// Print separator line
List<int> separator({bool bold = false}) { List<int> separator({bool bold = false}) {
return generator.text( return generator.text(
_separatorLine, _separatorLine,
@ -57,11 +90,11 @@ class ReceiptComponentBuilder {
align: PosAlign.center, align: PosAlign.center,
height: PosTextSize.size1, height: PosTextSize.size1,
width: PosTextSize.size1, width: PosTextSize.size1,
fontType: _font,
), ),
); );
} }
/// Print row with 2 columns (label: value)
List<int> row2Columns( List<int> row2Columns(
String leftText, String leftText,
String rightText, { String rightText, {
@ -73,17 +106,28 @@ class ReceiptComponentBuilder {
PosColumn( PosColumn(
text: leftText, text: leftText,
width: leftWidth, width: leftWidth,
styles: PosStyles(align: PosAlign.left, bold: bold), styles: PosStyles(
align: PosAlign.left,
bold: bold,
fontType: _font,
height: _bodySize,
width: _bodyWidth,
),
), ),
PosColumn( PosColumn(
text: rightText, text: rightText,
width: rightWidth, width: rightWidth,
styles: PosStyles(align: PosAlign.right, bold: bold), styles: PosStyles(
align: PosAlign.right,
bold: bold,
fontType: _font,
height: _bodySize,
width: _bodyWidth,
),
), ),
]); ]);
} }
/// Print row with 3 columns
List<int> row3Columns( List<int> row3Columns(
String leftText, String leftText,
String centerText, String centerText,
@ -111,55 +155,48 @@ class ReceiptComponentBuilder {
]); ]);
} }
/// Print empty lines /// Item text always size2 for kitchen/checker (prominent display)
List<int> emptyLines(int count) { List<int> itemText(String text, {bool bold = true}) {
return generator.emptyLines(count); return generator.text(
text,
styles: PosStyles(
bold: bold,
align: PosAlign.left,
fontType: _font,
height: PosTextSize.size2,
width: PosTextSize.size2,
),
);
} }
/// Print feed lines List<int> emptyLines(int count) => generator.emptyLines(count);
List<int> feed(int count) {
return generator.feed(count);
}
/// Helper: returns size2 for 80mm paper, size1 for 58mm List<int> feed(int count) => generator.feed(count);
PosTextSize get _titleSize =>
paperSize == 80 ? PosTextSize.size2 : PosTextSize.size1;
/// Print header (outlet info) // ---------------------------------------------------------------------------
// Composite components
// ---------------------------------------------------------------------------
/// Outlet header (receipt/cashier style)
List<int> header({ List<int> header({
required String outletName, required String outletName,
required String address, required String address,
required String phoneNumber, required String phoneNumber,
}) { }) {
List<int> bytes = []; List<int> bytes = [];
bytes += textCenter(outletName, bold: true, height: _titleSize, width: _titleSize);
bytes += textCenter(
outletName,
bold: true,
height: _titleSize,
width: _titleSize,
);
bytes += textCenter(address); bytes += textCenter(address);
bytes += textCenter(phoneNumber); bytes += textCenter(phoneNumber);
bytes += separator(); bytes += separator();
return bytes; return bytes;
} }
/// Centered printer type label (e.g. KITCHEN, BAR)
List<int> printerType({required String printerType}) { List<int> printerType({required String printerType}) {
List<int> bytes = []; return textCenter(printerType, bold: true, height: _titleSize, width: _titleSize);
bytes += textCenter(
printerType,
bold: true,
height: _titleSize,
width: _titleSize,
);
return bytes;
} }
/// Print date and time /// Date + time row (receipt style)
List<int> dateTime(DateTime dateTime) { List<int> dateTime(DateTime dateTime) {
return row2Columns( return row2Columns(
DateFormat('dd MMM yyyy').format(dateTime), DateFormat('dd MMM yyyy').format(dateTime),
@ -167,7 +204,22 @@ class ReceiptComponentBuilder {
); );
} }
/// Print order info section /// Order info block label : value style (checker/kitchen style)
List<int> orderInfoSimple({
required String orderNumber,
required String orderType,
required String cashierName,
}) {
List<int> bytes = [];
final dateStr = DateFormat('dd-MM-yyyy HH:mm').format(DateTime.now());
bytes += textLeft('Order : $orderNumber');
bytes += textLeft('Date : $dateStr');
bytes += textLeft('Purpose : $orderType');
bytes += textLeft('Waiter : $cashierName');
return bytes;
}
/// Order info block row2Columns style (receipt/cashier style)
List<int> orderInfo({ List<int> orderInfo({
required String orderNumber, required String orderNumber,
required String customerName, required String customerName,
@ -176,34 +228,32 @@ class ReceiptComponentBuilder {
String? tableNumber, String? tableNumber,
}) { }) {
List<int> bytes = []; List<int> bytes = [];
final dateStr = DateFormat('dd-MM-yyyy HH:mm').format(DateTime.now());
bytes += row2Columns('Nomor', orderNumber); bytes += textLeft('Order : $orderNumber');
bytes += row2Columns('Pelanggan', customerName); bytes += textLeft('Date : $dateStr');
bytes += row2Columns('Kasir', cashierName); if (tableNumber != null && tableNumber.isNotEmpty) {
bytes += textLeft('Table : $tableNumber');
}
bytes += textLeft('Waiter : $cashierName');
bytes += textLeft('Customer : $customerName');
if (paymentMethod != null) { if (paymentMethod != null) {
bytes += row2Columns('Pembayaran', paymentMethod); bytes += textLeft('Payment : $paymentMethod');
}
if (tableNumber != null && tableNumber.isNotEmpty) {
bytes += row2Columns('Meja', tableNumber);
} }
return bytes; return bytes;
} }
/// Print order type (Dine In, Take Away, etc) /// Order type banner (separator + type + separator)
List<int> orderType(String type) { List<int> orderType(String type) {
List<int> bytes = []; List<int> bytes = [];
bytes += separator(); bytes += separator();
bytes += textCenter(type, bold: true, height: _titleSize, width: _titleSize); bytes += textCenter(type, bold: true, height: _titleSize, width: _titleSize);
bytes += separator(); bytes += separator();
return bytes; return bytes;
} }
/// Print single item /// Single item row with price (receipt/cashier style)
List<int> orderItem({ List<int> orderItem({
required String productName, required String productName,
required int quantity, required int quantity,
@ -213,29 +263,19 @@ class ReceiptComponentBuilder {
String? notes, String? notes,
}) { }) {
List<int> bytes = []; List<int> bytes = [];
final displayName = (variantName != null && variantName.isNotEmpty) final displayName = (variantName != null && variantName.isNotEmpty)
? '$productName ($variantName)' ? '$productName ($variantName)'
: productName; : productName;
bytes += textLeft(displayName, bold: paperSize == 80); bytes += textLeft(displayName, bold: paperSize == 80);
bytes += row2Columns( bytes += row2Columns('$quantity x $unitPrice', totalPrice, leftWidth: 8, rightWidth: 4);
'$quantity x $unitPrice',
totalPrice,
leftWidth: 8,
rightWidth: 4,
);
if (notes != null && notes.isNotEmpty) { if (notes != null && notes.isNotEmpty) {
bytes += row2Columns('Note', notes, leftWidth: 4, rightWidth: 8); bytes += row2Columns('Note', notes, leftWidth: 4, rightWidth: 8);
} }
bytes += emptyLines(1); bytes += emptyLines(1);
return bytes; return bytes;
} }
/// Print summary section /// Summary section
List<int> summary({ List<int> summary({
required int totalItems, required int totalItems,
required String subtotal, required String subtotal,
@ -244,7 +284,6 @@ class ReceiptComponentBuilder {
required String paid, required String paid,
}) { }) {
List<int> bytes = []; List<int> bytes = [];
bytes += separator(); bytes += separator();
if (totalItems > 0) { if (totalItems > 0) {
bytes += row2Columns('Total Item', totalItems.toString()); bytes += row2Columns('Total Item', totalItems.toString());
@ -255,32 +294,23 @@ class ReceiptComponentBuilder {
bytes += row2Columns('Total', total, bold: true); bytes += row2Columns('Total', total, bold: true);
bytes += row2Columns('Bayar', paid); bytes += row2Columns('Bayar', paid);
bytes += separator(); bytes += separator();
return bytes; return bytes;
} }
/// Print footer (thank you message) /// Footer with thank-you message + cut
List<int> footer({String message = 'Terima kasih'}) { List<int> footer({String message = 'Terima kasih'}) {
List<int> bytes = []; List<int> bytes = [];
bytes += emptyLines(2); bytes += emptyLines(2);
bytes += textCenter( bytes += textCenter(message, bold: true, height: _titleSize, width: _titleSize);
message,
bold: true,
height: _titleSize,
width: _titleSize,
);
if (kDebugMode) { if (kDebugMode) {
bytes += textCenter("$paperSize MM"); bytes += textCenter('$paperSize MM');
} }
bytes += feed(paperSize == 80 ? 3 : 1); bytes += feed(paperSize == 80 ? 3 : 1);
bytes += generator.cut(); bytes += generator.cut();
return bytes; return bytes;
} }
/// Cut paper without printing footer message /// Feed + cut without any message
List<int> cutOnly() { List<int> cutOnly() {
List<int> bytes = []; List<int> bytes = [];
bytes += feed(paperSize == 80 ? 3 : 1); bytes += feed(paperSize == 80 ? 3 : 1);
@ -288,12 +318,14 @@ class ReceiptComponentBuilder {
return bytes; return bytes;
} }
/// Print QR Code // ---------------------------------------------------------------------------
// Media
// ---------------------------------------------------------------------------
List<int> qrCode(String data, {PosAlign align = PosAlign.center}) { List<int> qrCode(String data, {PosAlign align = PosAlign.center}) {
return generator.qrcode(data, align: align); return generator.qrcode(data, align: align);
} }
/// Print barcode
List<int> barcode(String data) { List<int> barcode(String data) {
return generator.barcode(Barcode.code128(data.codeUnits)); return generator.barcode(Barcode.code128(data.codeUnits));
} }

View File

@ -59,8 +59,6 @@ class HomeRightTitle extends StatelessWidget {
: (state.table?.tableName ?? '-'), : (state.table?.tableName ?? '-'),
Icons.table_restaurant_outlined, Icons.table_restaurant_outlined,
() { () {
if (state.table != null) return;
context.router.navigate( context.router.navigate(
const MainRoute(children: [TableRoute()]), const MainRoute(children: [TableRoute()]),
); );

View File

@ -3,7 +3,7 @@ description: "A new Flutter project."
publish_to: "none" publish_to: "none"
version: 1.0.6+11 version: 1.0.7+12
environment: environment:
sdk: ^3.8.1 sdk: ^3.8.1