From bf7a5708f5d19e4ebe795b06e8cdb5152f7accaf Mon Sep 17 00:00:00 2001 From: efrilm Date: Thu, 6 Nov 2025 18:15:54 +0700 Subject: [PATCH] print receipt --- .../printer_form/printer_form_bloc.dart | 45 ++- .../printer_form_bloc.freezed.dart | 297 +++++++++++++++++- .../printer_form/printer_form_event.dart | 4 + .../printer_form/printer_form_state.dart | 3 + lib/domain/order/entities/order_entity.dart | 86 +++++ .../printer/entities/printer_entity.dart | 17 + lib/domain/printer/printer.dart | 1 + .../repositories/i_printer_repository.dart | 4 + .../repositories/printer_repository.dart | 191 +++++++++++ lib/injection.config.dart | 15 +- .../components/print/print_ui.dart | 72 +++++ .../print/receipt_component_builder.dart | 270 ++++++++++++++++ .../printer/setting_printer_form.dart | 31 ++ .../printer/setting_printer_receipt.dart | 70 +++-- pubspec.lock | 56 ++++ pubspec.yaml | 2 + 16 files changed, 1132 insertions(+), 32 deletions(-) create mode 100644 lib/presentation/components/print/print_ui.dart create mode 100644 lib/presentation/components/print/receipt_component_builder.dart diff --git a/lib/application/printer/printer_form/printer_form_bloc.dart b/lib/application/printer/printer_form/printer_form_bloc.dart index d2ae572..fe9de51 100644 --- a/lib/application/printer/printer_form/printer_form_bloc.dart +++ b/lib/application/printer/printer_form/printer_form_bloc.dart @@ -1,10 +1,13 @@ import 'package:bloc/bloc.dart'; -import 'package:dartz/dartz.dart'; +import 'package:dartz/dartz.dart' hide Order; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:injectable/injectable.dart'; +import 'package:injectable/injectable.dart' hide Order; import '../../../common/function/app_function.dart'; +import '../../../domain/order/order.dart'; +import '../../../domain/outlet/outlet.dart'; import '../../../domain/printer/printer.dart'; +import '../../../presentation/components/print/print_ui.dart'; part 'printer_form_event.dart'; part 'printer_form_state.dart'; @@ -13,7 +16,10 @@ part 'printer_form_bloc.freezed.dart'; @injectable class PrinterFormBloc extends Bloc { final IPrinterRepository _printerRepository; - PrinterFormBloc(this._printerRepository) : super(PrinterFormState.initial()) { + final IOutletRepository _outletRepository; + + PrinterFormBloc(this._printerRepository, this._outletRepository) + : super(PrinterFormState.initial()) { on(_onPrinterFormEvent); } @@ -104,6 +110,39 @@ class PrinterFormBloc extends Bloc { ), ); }, + printTest: (e) async { + Either failureOrPrinter; + + emit(state.copyWith(isPrinting: true, failureOrPrintTest: none())); + + final currentOutlet = await _outletRepository.currentOutlet(); + + final printValue = await PrintUi().printOrder( + order: Order.mockOrder(), + outlet: currentOutlet, + cashierName: 'Kasir Test', + ); + + final printer = Printer.fromTest( + code: e.code, + name: state.name, + address: e.macAccdress, + paper: state.paper, + type: state.type, + ); + + failureOrPrinter = await _printerRepository.printStruct( + printer, + printValue, + ); + + emit( + state.copyWith( + isPrinting: false, + failureOrPrintTest: optionOf(failureOrPrinter), + ), + ); + }, ); } } diff --git a/lib/application/printer/printer_form/printer_form_bloc.freezed.dart b/lib/application/printer/printer_form/printer_form_bloc.freezed.dart index ee641d4..9a3a5e2 100644 --- a/lib/application/printer/printer_form/printer_form_bloc.freezed.dart +++ b/lib/application/printer/printer_form/printer_form_bloc.freezed.dart @@ -27,6 +27,7 @@ mixin _$PrinterFormEvent { required TResult Function() created, required TResult Function(int id) updated, required TResult Function(int id) deleted, + required TResult Function(String code, String macAccdress) printTest, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ @@ -38,6 +39,7 @@ mixin _$PrinterFormEvent { TResult? Function()? created, TResult? Function(int id)? updated, TResult? Function(int id)? deleted, + TResult? Function(String code, String macAccdress)? printTest, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ @@ -49,6 +51,7 @@ mixin _$PrinterFormEvent { TResult Function()? created, TResult Function(int id)? updated, TResult Function(int id)? deleted, + TResult Function(String code, String macAccdress)? printTest, required TResult orElse(), }) => throw _privateConstructorUsedError; @optionalTypeArgs @@ -61,6 +64,7 @@ mixin _$PrinterFormEvent { required TResult Function(_Created value) created, required TResult Function(_Updated value) updated, required TResult Function(_Deleted value) deleted, + required TResult Function(_PrintTest value) printTest, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? mapOrNull({ @@ -72,6 +76,7 @@ mixin _$PrinterFormEvent { TResult? Function(_Created value)? created, TResult? Function(_Updated value)? updated, TResult? Function(_Deleted value)? deleted, + TResult? Function(_PrintTest value)? printTest, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeMap({ @@ -83,6 +88,7 @@ mixin _$PrinterFormEvent { TResult Function(_Created value)? created, TResult Function(_Updated value)? updated, TResult Function(_Deleted value)? deleted, + TResult Function(_PrintTest value)? printTest, required TResult orElse(), }) => throw _privateConstructorUsedError; } @@ -187,6 +193,7 @@ class _$CodeChangedImpl implements _CodeChanged { required TResult Function() created, required TResult Function(int id) updated, required TResult Function(int id) deleted, + required TResult Function(String code, String macAccdress) printTest, }) { return codeChanged(code); } @@ -202,6 +209,7 @@ class _$CodeChangedImpl implements _CodeChanged { TResult? Function()? created, TResult? Function(int id)? updated, TResult? Function(int id)? deleted, + TResult? Function(String code, String macAccdress)? printTest, }) { return codeChanged?.call(code); } @@ -217,6 +225,7 @@ class _$CodeChangedImpl implements _CodeChanged { TResult Function()? created, TResult Function(int id)? updated, TResult Function(int id)? deleted, + TResult Function(String code, String macAccdress)? printTest, required TResult orElse(), }) { if (codeChanged != null) { @@ -236,6 +245,7 @@ class _$CodeChangedImpl implements _CodeChanged { required TResult Function(_Created value) created, required TResult Function(_Updated value) updated, required TResult Function(_Deleted value) deleted, + required TResult Function(_PrintTest value) printTest, }) { return codeChanged(this); } @@ -251,6 +261,7 @@ class _$CodeChangedImpl implements _CodeChanged { TResult? Function(_Created value)? created, TResult? Function(_Updated value)? updated, TResult? Function(_Deleted value)? deleted, + TResult? Function(_PrintTest value)? printTest, }) { return codeChanged?.call(this); } @@ -266,6 +277,7 @@ class _$CodeChangedImpl implements _CodeChanged { TResult Function(_Created value)? created, TResult Function(_Updated value)? updated, TResult Function(_Deleted value)? deleted, + TResult Function(_PrintTest value)? printTest, required TResult orElse(), }) { if (codeChanged != null) { @@ -365,6 +377,7 @@ class _$NameChangedImpl implements _NameChanged { required TResult Function() created, required TResult Function(int id) updated, required TResult Function(int id) deleted, + required TResult Function(String code, String macAccdress) printTest, }) { return nameChanged(name); } @@ -380,6 +393,7 @@ class _$NameChangedImpl implements _NameChanged { TResult? Function()? created, TResult? Function(int id)? updated, TResult? Function(int id)? deleted, + TResult? Function(String code, String macAccdress)? printTest, }) { return nameChanged?.call(name); } @@ -395,6 +409,7 @@ class _$NameChangedImpl implements _NameChanged { TResult Function()? created, TResult Function(int id)? updated, TResult Function(int id)? deleted, + TResult Function(String code, String macAccdress)? printTest, required TResult orElse(), }) { if (nameChanged != null) { @@ -414,6 +429,7 @@ class _$NameChangedImpl implements _NameChanged { required TResult Function(_Created value) created, required TResult Function(_Updated value) updated, required TResult Function(_Deleted value) deleted, + required TResult Function(_PrintTest value) printTest, }) { return nameChanged(this); } @@ -429,6 +445,7 @@ class _$NameChangedImpl implements _NameChanged { TResult? Function(_Created value)? created, TResult? Function(_Updated value)? updated, TResult? Function(_Deleted value)? deleted, + TResult? Function(_PrintTest value)? printTest, }) { return nameChanged?.call(this); } @@ -444,6 +461,7 @@ class _$NameChangedImpl implements _NameChanged { TResult Function(_Created value)? created, TResult Function(_Updated value)? updated, TResult Function(_Deleted value)? deleted, + TResult Function(_PrintTest value)? printTest, required TResult orElse(), }) { if (nameChanged != null) { @@ -546,6 +564,7 @@ class _$AddressChangedImpl implements _AddressChanged { required TResult Function() created, required TResult Function(int id) updated, required TResult Function(int id) deleted, + required TResult Function(String code, String macAccdress) printTest, }) { return addressChanged(address); } @@ -561,6 +580,7 @@ class _$AddressChangedImpl implements _AddressChanged { TResult? Function()? created, TResult? Function(int id)? updated, TResult? Function(int id)? deleted, + TResult? Function(String code, String macAccdress)? printTest, }) { return addressChanged?.call(address); } @@ -576,6 +596,7 @@ class _$AddressChangedImpl implements _AddressChanged { TResult Function()? created, TResult Function(int id)? updated, TResult Function(int id)? deleted, + TResult Function(String code, String macAccdress)? printTest, required TResult orElse(), }) { if (addressChanged != null) { @@ -595,6 +616,7 @@ class _$AddressChangedImpl implements _AddressChanged { required TResult Function(_Created value) created, required TResult Function(_Updated value) updated, required TResult Function(_Deleted value) deleted, + required TResult Function(_PrintTest value) printTest, }) { return addressChanged(this); } @@ -610,6 +632,7 @@ class _$AddressChangedImpl implements _AddressChanged { TResult? Function(_Created value)? created, TResult? Function(_Updated value)? updated, TResult? Function(_Deleted value)? deleted, + TResult? Function(_PrintTest value)? printTest, }) { return addressChanged?.call(this); } @@ -625,6 +648,7 @@ class _$AddressChangedImpl implements _AddressChanged { TResult Function(_Created value)? created, TResult Function(_Updated value)? updated, TResult Function(_Deleted value)? deleted, + TResult Function(_PrintTest value)? printTest, required TResult orElse(), }) { if (addressChanged != null) { @@ -724,6 +748,7 @@ class _$TypeChangedImpl implements _TypeChanged { required TResult Function() created, required TResult Function(int id) updated, required TResult Function(int id) deleted, + required TResult Function(String code, String macAccdress) printTest, }) { return typeChanged(type); } @@ -739,6 +764,7 @@ class _$TypeChangedImpl implements _TypeChanged { TResult? Function()? created, TResult? Function(int id)? updated, TResult? Function(int id)? deleted, + TResult? Function(String code, String macAccdress)? printTest, }) { return typeChanged?.call(type); } @@ -754,6 +780,7 @@ class _$TypeChangedImpl implements _TypeChanged { TResult Function()? created, TResult Function(int id)? updated, TResult Function(int id)? deleted, + TResult Function(String code, String macAccdress)? printTest, required TResult orElse(), }) { if (typeChanged != null) { @@ -773,6 +800,7 @@ class _$TypeChangedImpl implements _TypeChanged { required TResult Function(_Created value) created, required TResult Function(_Updated value) updated, required TResult Function(_Deleted value) deleted, + required TResult Function(_PrintTest value) printTest, }) { return typeChanged(this); } @@ -788,6 +816,7 @@ class _$TypeChangedImpl implements _TypeChanged { TResult? Function(_Created value)? created, TResult? Function(_Updated value)? updated, TResult? Function(_Deleted value)? deleted, + TResult? Function(_PrintTest value)? printTest, }) { return typeChanged?.call(this); } @@ -803,6 +832,7 @@ class _$TypeChangedImpl implements _TypeChanged { TResult Function(_Created value)? created, TResult Function(_Updated value)? updated, TResult Function(_Deleted value)? deleted, + TResult Function(_PrintTest value)? printTest, required TResult orElse(), }) { if (typeChanged != null) { @@ -902,6 +932,7 @@ class _$PaperChangedImpl implements _PaperChanged { required TResult Function() created, required TResult Function(int id) updated, required TResult Function(int id) deleted, + required TResult Function(String code, String macAccdress) printTest, }) { return paperChanged(paper); } @@ -917,6 +948,7 @@ class _$PaperChangedImpl implements _PaperChanged { TResult? Function()? created, TResult? Function(int id)? updated, TResult? Function(int id)? deleted, + TResult? Function(String code, String macAccdress)? printTest, }) { return paperChanged?.call(paper); } @@ -932,6 +964,7 @@ class _$PaperChangedImpl implements _PaperChanged { TResult Function()? created, TResult Function(int id)? updated, TResult Function(int id)? deleted, + TResult Function(String code, String macAccdress)? printTest, required TResult orElse(), }) { if (paperChanged != null) { @@ -951,6 +984,7 @@ class _$PaperChangedImpl implements _PaperChanged { required TResult Function(_Created value) created, required TResult Function(_Updated value) updated, required TResult Function(_Deleted value) deleted, + required TResult Function(_PrintTest value) printTest, }) { return paperChanged(this); } @@ -966,6 +1000,7 @@ class _$PaperChangedImpl implements _PaperChanged { TResult? Function(_Created value)? created, TResult? Function(_Updated value)? updated, TResult? Function(_Deleted value)? deleted, + TResult? Function(_PrintTest value)? printTest, }) { return paperChanged?.call(this); } @@ -981,6 +1016,7 @@ class _$PaperChangedImpl implements _PaperChanged { TResult Function(_Created value)? created, TResult Function(_Updated value)? updated, TResult Function(_Deleted value)? deleted, + TResult Function(_PrintTest value)? printTest, required TResult orElse(), }) { if (paperChanged != null) { @@ -1053,6 +1089,7 @@ class _$CreatedImpl implements _Created { required TResult Function() created, required TResult Function(int id) updated, required TResult Function(int id) deleted, + required TResult Function(String code, String macAccdress) printTest, }) { return created(); } @@ -1068,6 +1105,7 @@ class _$CreatedImpl implements _Created { TResult? Function()? created, TResult? Function(int id)? updated, TResult? Function(int id)? deleted, + TResult? Function(String code, String macAccdress)? printTest, }) { return created?.call(); } @@ -1083,6 +1121,7 @@ class _$CreatedImpl implements _Created { TResult Function()? created, TResult Function(int id)? updated, TResult Function(int id)? deleted, + TResult Function(String code, String macAccdress)? printTest, required TResult orElse(), }) { if (created != null) { @@ -1102,6 +1141,7 @@ class _$CreatedImpl implements _Created { required TResult Function(_Created value) created, required TResult Function(_Updated value) updated, required TResult Function(_Deleted value) deleted, + required TResult Function(_PrintTest value) printTest, }) { return created(this); } @@ -1117,6 +1157,7 @@ class _$CreatedImpl implements _Created { TResult? Function(_Created value)? created, TResult? Function(_Updated value)? updated, TResult? Function(_Deleted value)? deleted, + TResult? Function(_PrintTest value)? printTest, }) { return created?.call(this); } @@ -1132,6 +1173,7 @@ class _$CreatedImpl implements _Created { TResult Function(_Created value)? created, TResult Function(_Updated value)? updated, TResult Function(_Deleted value)? deleted, + TResult Function(_PrintTest value)? printTest, required TResult orElse(), }) { if (created != null) { @@ -1223,6 +1265,7 @@ class _$UpdatedImpl implements _Updated { required TResult Function() created, required TResult Function(int id) updated, required TResult Function(int id) deleted, + required TResult Function(String code, String macAccdress) printTest, }) { return updated(id); } @@ -1238,6 +1281,7 @@ class _$UpdatedImpl implements _Updated { TResult? Function()? created, TResult? Function(int id)? updated, TResult? Function(int id)? deleted, + TResult? Function(String code, String macAccdress)? printTest, }) { return updated?.call(id); } @@ -1253,6 +1297,7 @@ class _$UpdatedImpl implements _Updated { TResult Function()? created, TResult Function(int id)? updated, TResult Function(int id)? deleted, + TResult Function(String code, String macAccdress)? printTest, required TResult orElse(), }) { if (updated != null) { @@ -1272,6 +1317,7 @@ class _$UpdatedImpl implements _Updated { required TResult Function(_Created value) created, required TResult Function(_Updated value) updated, required TResult Function(_Deleted value) deleted, + required TResult Function(_PrintTest value) printTest, }) { return updated(this); } @@ -1287,6 +1333,7 @@ class _$UpdatedImpl implements _Updated { TResult? Function(_Created value)? created, TResult? Function(_Updated value)? updated, TResult? Function(_Deleted value)? deleted, + TResult? Function(_PrintTest value)? printTest, }) { return updated?.call(this); } @@ -1302,6 +1349,7 @@ class _$UpdatedImpl implements _Updated { TResult Function(_Created value)? created, TResult Function(_Updated value)? updated, TResult Function(_Deleted value)? deleted, + TResult Function(_PrintTest value)? printTest, required TResult orElse(), }) { if (updated != null) { @@ -1401,6 +1449,7 @@ class _$DeletedImpl implements _Deleted { required TResult Function() created, required TResult Function(int id) updated, required TResult Function(int id) deleted, + required TResult Function(String code, String macAccdress) printTest, }) { return deleted(id); } @@ -1416,6 +1465,7 @@ class _$DeletedImpl implements _Deleted { TResult? Function()? created, TResult? Function(int id)? updated, TResult? Function(int id)? deleted, + TResult? Function(String code, String macAccdress)? printTest, }) { return deleted?.call(id); } @@ -1431,6 +1481,7 @@ class _$DeletedImpl implements _Deleted { TResult Function()? created, TResult Function(int id)? updated, TResult Function(int id)? deleted, + TResult Function(String code, String macAccdress)? printTest, required TResult orElse(), }) { if (deleted != null) { @@ -1450,6 +1501,7 @@ class _$DeletedImpl implements _Deleted { required TResult Function(_Created value) created, required TResult Function(_Updated value) updated, required TResult Function(_Deleted value) deleted, + required TResult Function(_PrintTest value) printTest, }) { return deleted(this); } @@ -1465,6 +1517,7 @@ class _$DeletedImpl implements _Deleted { TResult? Function(_Created value)? created, TResult? Function(_Updated value)? updated, TResult? Function(_Deleted value)? deleted, + TResult? Function(_PrintTest value)? printTest, }) { return deleted?.call(this); } @@ -1480,6 +1533,7 @@ class _$DeletedImpl implements _Deleted { TResult Function(_Created value)? created, TResult Function(_Updated value)? updated, TResult Function(_Deleted value)? deleted, + TResult Function(_PrintTest value)? printTest, required TResult orElse(), }) { if (deleted != null) { @@ -1501,6 +1555,202 @@ abstract class _Deleted implements PrinterFormEvent { throw _privateConstructorUsedError; } +/// @nodoc +abstract class _$$PrintTestImplCopyWith<$Res> { + factory _$$PrintTestImplCopyWith( + _$PrintTestImpl value, + $Res Function(_$PrintTestImpl) then, + ) = __$$PrintTestImplCopyWithImpl<$Res>; + @useResult + $Res call({String code, String macAccdress}); +} + +/// @nodoc +class __$$PrintTestImplCopyWithImpl<$Res> + extends _$PrinterFormEventCopyWithImpl<$Res, _$PrintTestImpl> + implements _$$PrintTestImplCopyWith<$Res> { + __$$PrintTestImplCopyWithImpl( + _$PrintTestImpl _value, + $Res Function(_$PrintTestImpl) _then, + ) : super(_value, _then); + + /// Create a copy of PrinterFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? code = null, Object? macAccdress = null}) { + return _then( + _$PrintTestImpl( + code: null == code + ? _value.code + : code // ignore: cast_nullable_to_non_nullable + as String, + macAccdress: null == macAccdress + ? _value.macAccdress + : macAccdress // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$PrintTestImpl implements _PrintTest { + const _$PrintTestImpl({required this.code, required this.macAccdress}); + + @override + final String code; + @override + final String macAccdress; + + @override + String toString() { + return 'PrinterFormEvent.printTest(code: $code, macAccdress: $macAccdress)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PrintTestImpl && + (identical(other.code, code) || other.code == code) && + (identical(other.macAccdress, macAccdress) || + other.macAccdress == macAccdress)); + } + + @override + int get hashCode => Object.hash(runtimeType, code, macAccdress); + + /// Create a copy of PrinterFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$PrintTestImplCopyWith<_$PrintTestImpl> get copyWith => + __$$PrintTestImplCopyWithImpl<_$PrintTestImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String code) codeChanged, + required TResult Function(String name) nameChanged, + required TResult Function(String address) addressChanged, + required TResult Function(String type) typeChanged, + required TResult Function(String paper) paperChanged, + required TResult Function() created, + required TResult Function(int id) updated, + required TResult Function(int id) deleted, + required TResult Function(String code, String macAccdress) printTest, + }) { + return printTest(code, macAccdress); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String code)? codeChanged, + TResult? Function(String name)? nameChanged, + TResult? Function(String address)? addressChanged, + TResult? Function(String type)? typeChanged, + TResult? Function(String paper)? paperChanged, + TResult? Function()? created, + TResult? Function(int id)? updated, + TResult? Function(int id)? deleted, + TResult? Function(String code, String macAccdress)? printTest, + }) { + return printTest?.call(code, macAccdress); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String code)? codeChanged, + TResult Function(String name)? nameChanged, + TResult Function(String address)? addressChanged, + TResult Function(String type)? typeChanged, + TResult Function(String paper)? paperChanged, + TResult Function()? created, + TResult Function(int id)? updated, + TResult Function(int id)? deleted, + TResult Function(String code, String macAccdress)? printTest, + required TResult orElse(), + }) { + if (printTest != null) { + return printTest(code, macAccdress); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_CodeChanged value) codeChanged, + required TResult Function(_NameChanged value) nameChanged, + required TResult Function(_AddressChanged value) addressChanged, + required TResult Function(_TypeChanged value) typeChanged, + required TResult Function(_PaperChanged value) paperChanged, + required TResult Function(_Created value) created, + required TResult Function(_Updated value) updated, + required TResult Function(_Deleted value) deleted, + required TResult Function(_PrintTest value) printTest, + }) { + return printTest(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_CodeChanged value)? codeChanged, + TResult? Function(_NameChanged value)? nameChanged, + TResult? Function(_AddressChanged value)? addressChanged, + TResult? Function(_TypeChanged value)? typeChanged, + TResult? Function(_PaperChanged value)? paperChanged, + TResult? Function(_Created value)? created, + TResult? Function(_Updated value)? updated, + TResult? Function(_Deleted value)? deleted, + TResult? Function(_PrintTest value)? printTest, + }) { + return printTest?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_CodeChanged value)? codeChanged, + TResult Function(_NameChanged value)? nameChanged, + TResult Function(_AddressChanged value)? addressChanged, + TResult Function(_TypeChanged value)? typeChanged, + TResult Function(_PaperChanged value)? paperChanged, + TResult Function(_Created value)? created, + TResult Function(_Updated value)? updated, + TResult Function(_Deleted value)? deleted, + TResult Function(_PrintTest value)? printTest, + required TResult orElse(), + }) { + if (printTest != null) { + return printTest(this); + } + return orElse(); + } +} + +abstract class _PrintTest implements PrinterFormEvent { + const factory _PrintTest({ + required final String code, + required final String macAccdress, + }) = _$PrintTestImpl; + + String get code; + String get macAccdress; + + /// Create a copy of PrinterFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$PrintTestImplCopyWith<_$PrintTestImpl> get copyWith => + throw _privateConstructorUsedError; +} + /// @nodoc mixin _$PrinterFormState { String get code => throw _privateConstructorUsedError; @@ -1514,9 +1764,12 @@ mixin _$PrinterFormState { throw _privateConstructorUsedError; Option> get failureOrDeleteSuccess => throw _privateConstructorUsedError; + Option> get failureOrPrintTest => + throw _privateConstructorUsedError; dynamic get isCreating => throw _privateConstructorUsedError; dynamic get isUpdate => throw _privateConstructorUsedError; dynamic get isDeleting => throw _privateConstructorUsedError; + dynamic get isPrinting => throw _privateConstructorUsedError; /// Create a copy of PrinterFormState /// with the given fields replaced by the non-null parameter values. @@ -1541,9 +1794,11 @@ abstract class $PrinterFormStateCopyWith<$Res> { Option> failureOrCreateSuccess, Option> failureOrUpdateSuccess, Option> failureOrDeleteSuccess, + Option> failureOrPrintTest, dynamic isCreating, dynamic isUpdate, dynamic isDeleting, + dynamic isPrinting, }); } @@ -1570,9 +1825,11 @@ class _$PrinterFormStateCopyWithImpl<$Res, $Val extends PrinterFormState> Object? failureOrCreateSuccess = null, Object? failureOrUpdateSuccess = null, Object? failureOrDeleteSuccess = null, + Object? failureOrPrintTest = null, Object? isCreating = freezed, Object? isUpdate = freezed, Object? isDeleting = freezed, + Object? isPrinting = freezed, }) { return _then( _value.copyWith( @@ -1608,6 +1865,10 @@ class _$PrinterFormStateCopyWithImpl<$Res, $Val extends PrinterFormState> ? _value.failureOrDeleteSuccess : failureOrDeleteSuccess // ignore: cast_nullable_to_non_nullable as Option>, + failureOrPrintTest: null == failureOrPrintTest + ? _value.failureOrPrintTest + : failureOrPrintTest // ignore: cast_nullable_to_non_nullable + as Option>, isCreating: freezed == isCreating ? _value.isCreating : isCreating // ignore: cast_nullable_to_non_nullable @@ -1620,6 +1881,10 @@ class _$PrinterFormStateCopyWithImpl<$Res, $Val extends PrinterFormState> ? _value.isDeleting : isDeleting // ignore: cast_nullable_to_non_nullable as dynamic, + isPrinting: freezed == isPrinting + ? _value.isPrinting + : isPrinting // ignore: cast_nullable_to_non_nullable + as dynamic, ) as $Val, ); @@ -1644,9 +1909,11 @@ abstract class _$$PrinterFormStateImplCopyWith<$Res> Option> failureOrCreateSuccess, Option> failureOrUpdateSuccess, Option> failureOrDeleteSuccess, + Option> failureOrPrintTest, dynamic isCreating, dynamic isUpdate, dynamic isDeleting, + dynamic isPrinting, }); } @@ -1672,9 +1939,11 @@ class __$$PrinterFormStateImplCopyWithImpl<$Res> Object? failureOrCreateSuccess = null, Object? failureOrUpdateSuccess = null, Object? failureOrDeleteSuccess = null, + Object? failureOrPrintTest = null, Object? isCreating = freezed, Object? isUpdate = freezed, Object? isDeleting = freezed, + Object? isPrinting = freezed, }) { return _then( _$PrinterFormStateImpl( @@ -1710,9 +1979,14 @@ class __$$PrinterFormStateImplCopyWithImpl<$Res> ? _value.failureOrDeleteSuccess : failureOrDeleteSuccess // ignore: cast_nullable_to_non_nullable as Option>, + failureOrPrintTest: null == failureOrPrintTest + ? _value.failureOrPrintTest + : failureOrPrintTest // ignore: cast_nullable_to_non_nullable + as Option>, isCreating: freezed == isCreating ? _value.isCreating! : isCreating, isUpdate: freezed == isUpdate ? _value.isUpdate! : isUpdate, isDeleting: freezed == isDeleting ? _value.isDeleting! : isDeleting, + isPrinting: freezed == isPrinting ? _value.isPrinting! : isPrinting, ), ); } @@ -1730,9 +2004,11 @@ class _$PrinterFormStateImpl implements _PrinterFormState { required this.failureOrCreateSuccess, required this.failureOrUpdateSuccess, required this.failureOrDeleteSuccess, + required this.failureOrPrintTest, this.isCreating = false, this.isUpdate = false, this.isDeleting = false, + this.isPrinting = false, }); @override @@ -1752,6 +2028,8 @@ class _$PrinterFormStateImpl implements _PrinterFormState { @override final Option> failureOrDeleteSuccess; @override + final Option> failureOrPrintTest; + @override @JsonKey() final dynamic isCreating; @override @@ -1760,10 +2038,13 @@ class _$PrinterFormStateImpl implements _PrinterFormState { @override @JsonKey() final dynamic isDeleting; + @override + @JsonKey() + final dynamic isPrinting; @override String toString() { - return 'PrinterFormState(code: $code, name: $name, address: $address, paper: $paper, type: $type, failureOrCreateSuccess: $failureOrCreateSuccess, failureOrUpdateSuccess: $failureOrUpdateSuccess, failureOrDeleteSuccess: $failureOrDeleteSuccess, isCreating: $isCreating, isUpdate: $isUpdate, isDeleting: $isDeleting)'; + return 'PrinterFormState(code: $code, name: $name, address: $address, paper: $paper, type: $type, failureOrCreateSuccess: $failureOrCreateSuccess, failureOrUpdateSuccess: $failureOrUpdateSuccess, failureOrDeleteSuccess: $failureOrDeleteSuccess, failureOrPrintTest: $failureOrPrintTest, isCreating: $isCreating, isUpdate: $isUpdate, isDeleting: $isDeleting, isPrinting: $isPrinting)'; } @override @@ -1782,6 +2063,8 @@ class _$PrinterFormStateImpl implements _PrinterFormState { other.failureOrUpdateSuccess == failureOrUpdateSuccess) && (identical(other.failureOrDeleteSuccess, failureOrDeleteSuccess) || other.failureOrDeleteSuccess == failureOrDeleteSuccess) && + (identical(other.failureOrPrintTest, failureOrPrintTest) || + other.failureOrPrintTest == failureOrPrintTest) && const DeepCollectionEquality().equals( other.isCreating, isCreating, @@ -1790,6 +2073,10 @@ class _$PrinterFormStateImpl implements _PrinterFormState { const DeepCollectionEquality().equals( other.isDeleting, isDeleting, + ) && + const DeepCollectionEquality().equals( + other.isPrinting, + isPrinting, )); } @@ -1804,9 +2091,11 @@ class _$PrinterFormStateImpl implements _PrinterFormState { failureOrCreateSuccess, failureOrUpdateSuccess, failureOrDeleteSuccess, + failureOrPrintTest, const DeepCollectionEquality().hash(isCreating), const DeepCollectionEquality().hash(isUpdate), const DeepCollectionEquality().hash(isDeleting), + const DeepCollectionEquality().hash(isPrinting), ); /// Create a copy of PrinterFormState @@ -1831,9 +2120,11 @@ abstract class _PrinterFormState implements PrinterFormState { required final Option> failureOrCreateSuccess, required final Option> failureOrUpdateSuccess, required final Option> failureOrDeleteSuccess, + required final Option> failureOrPrintTest, final dynamic isCreating, final dynamic isUpdate, final dynamic isDeleting, + final dynamic isPrinting, }) = _$PrinterFormStateImpl; @override @@ -1853,11 +2144,15 @@ abstract class _PrinterFormState implements PrinterFormState { @override Option> get failureOrDeleteSuccess; @override + Option> get failureOrPrintTest; + @override dynamic get isCreating; @override dynamic get isUpdate; @override dynamic get isDeleting; + @override + dynamic get isPrinting; /// Create a copy of PrinterFormState /// with the given fields replaced by the non-null parameter values. diff --git a/lib/application/printer/printer_form/printer_form_event.dart b/lib/application/printer/printer_form/printer_form_event.dart index 61e55da..e62d40e 100644 --- a/lib/application/printer/printer_form/printer_form_event.dart +++ b/lib/application/printer/printer_form/printer_form_event.dart @@ -11,4 +11,8 @@ class PrinterFormEvent with _$PrinterFormEvent { const factory PrinterFormEvent.created() = _Created; const factory PrinterFormEvent.updated(int id) = _Updated; const factory PrinterFormEvent.deleted(int id) = _Deleted; + const factory PrinterFormEvent.printTest({ + required String code, + required String macAccdress, + }) = _PrintTest; } diff --git a/lib/application/printer/printer_form/printer_form_state.dart b/lib/application/printer/printer_form/printer_form_state.dart index edb7b8e..31ce059 100644 --- a/lib/application/printer/printer_form/printer_form_state.dart +++ b/lib/application/printer/printer_form/printer_form_state.dart @@ -11,9 +11,11 @@ class PrinterFormState with _$PrinterFormState { required Option> failureOrCreateSuccess, required Option> failureOrUpdateSuccess, required Option> failureOrDeleteSuccess, + required Option> failureOrPrintTest, @Default(false) isCreating, @Default(false) isUpdate, @Default(false) isDeleting, + @Default(false) isPrinting, }) = _PrinterFormState; factory PrinterFormState.initial() => PrinterFormState( @@ -25,5 +27,6 @@ class PrinterFormState with _$PrinterFormState { failureOrCreateSuccess: none(), failureOrUpdateSuccess: none(), failureOrDeleteSuccess: none(), + failureOrPrintTest: none(), ); } diff --git a/lib/domain/order/entities/order_entity.dart b/lib/domain/order/entities/order_entity.dart index e26dfe1..277baf4 100644 --- a/lib/domain/order/entities/order_entity.dart +++ b/lib/domain/order/entities/order_entity.dart @@ -75,6 +75,92 @@ class Order with _$Order { splitType: '', refundReason: '', ); + + factory Order.mockOrder() => Order( + id: 'ORD-001', + orderNumber: 'A-1001', + outletId: 'OUTLET-001', + userId: 'USER-001', + tableNumber: 'T01', + orderType: 'Dine In', + status: 'Completed', + subtotal: 150000, + taxAmount: 15000, + discountAmount: 5000, + totalAmount: 160000, + totalCost: 100000, + remainingAmount: 0, + paymentStatus: 'Paid', + refundAmount: 0, + refundReason: '', + isVoid: false, + isRefund: false, + notes: 'Customer requested less sugar', + metadata: {'source': 'POS', 'device': 'iPad', 'customer_name': "John Doe"}, + createdAt: DateTime.now().subtract(const Duration(hours: 1)), + updatedAt: DateTime.now(), + orderItems: [ + OrderItem( + id: 'ITEM-001', + orderId: 'ORD-001', + productId: 'PROD-001', + productName: 'Cappuccino', + productVariantId: 'VAR-001', + productVariantName: 'Hot', + quantity: 2, + unitPrice: 25000, + totalPrice: 50000, + modifiers: [ + {'name': 'Extra Shot', 'price': 5000}, + ], + notes: 'No sugar', + status: 'Served', + createdAt: DateTime.now().subtract(const Duration(hours: 1)), + updatedAt: DateTime.now(), + printerType: 'Barista', + paidQuantity: 2, + ), + OrderItem( + id: 'ITEM-002', + orderId: 'ORD-001', + productId: 'PROD-002', + productName: 'Spaghetti Carbonara', + productVariantId: 'VAR-002', + productVariantName: '', + quantity: 1, + unitPrice: 55000, + totalPrice: 55000, + modifiers: [], + notes: '', + status: 'Served', + createdAt: DateTime.now().subtract(const Duration(hours: 1)), + updatedAt: DateTime.now(), + printerType: 'Kitchen', + paidQuantity: 1, + ), + ], + payments: [ + Payment( + id: 'PAY-001', + orderId: 'ORD-001', + paymentMethodId: 'PM-CASH', + paymentMethodName: 'Cash', + paymentMethodType: 'Cash', + amount: 160000, + status: 'Success', + splitNumber: 1, + splitTotal: 1, + splitDescription: 'Full payment by cash', + refundAmount: 0, + metadata: {'cashier': 'Efril', 'device': 'POS iPad'}, + createdAt: DateTime.now().subtract(const Duration(minutes: 45)), + updatedAt: DateTime.now(), + ), + ], + totalPaid: 160000, + paymentCount: 1, + splitType: 'Single', + ); } @freezed diff --git a/lib/domain/printer/entities/printer_entity.dart b/lib/domain/printer/entities/printer_entity.dart index 52e6371..eb7eb60 100644 --- a/lib/domain/printer/entities/printer_entity.dart +++ b/lib/domain/printer/entities/printer_entity.dart @@ -23,4 +23,21 @@ class Printer with _$Printer { createdAt: DateTime.now(), updatedAt: DateTime.now(), ); + + factory Printer.fromTest({ + required String code, + required String name, + required String address, + required String paper, + required String type, + }) => Printer( + id: generateRandomNumber(), + code: code, + name: name, + address: address, + paper: paper, + type: type, + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + ); } diff --git a/lib/domain/printer/printer.dart b/lib/domain/printer/printer.dart index 07072d3..dc20b0f 100644 --- a/lib/domain/printer/printer.dart +++ b/lib/domain/printer/printer.dart @@ -3,6 +3,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:print_bluetooth_thermal/print_bluetooth_thermal.dart'; import '../../common/api/api_failure.dart'; +import '../../common/function/app_function.dart'; part 'printer.freezed.dart'; diff --git a/lib/domain/printer/repositories/i_printer_repository.dart b/lib/domain/printer/repositories/i_printer_repository.dart index 88a09ec..7f5b016 100644 --- a/lib/domain/printer/repositories/i_printer_repository.dart +++ b/lib/domain/printer/repositories/i_printer_repository.dart @@ -10,4 +10,8 @@ abstract class IPrinterRepository { Future> updatePrinter(Printer printer, int id); Future> deletePrinter(int id); Future> getPrinterByCode(String code); + Future> printStruct( + Printer printer, + List printValue, + ); } diff --git a/lib/infrastructure/printer/repositories/printer_repository.dart b/lib/infrastructure/printer/repositories/printer_repository.dart index defb89c..01f9b36 100644 --- a/lib/infrastructure/printer/repositories/printer_repository.dart +++ b/lib/infrastructure/printer/repositories/printer_repository.dart @@ -2,6 +2,7 @@ import 'dart:developer'; import 'package:dartz/dartz.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; +import 'package:flutter_esc_pos_network/flutter_esc_pos_network.dart'; import 'package:injectable/injectable.dart'; import 'package:print_bluetooth_thermal/print_bluetooth_thermal.dart'; @@ -194,4 +195,194 @@ class PrinterRepository implements IPrinterRepository { return left(const PrinterFailure.unexpectedError()); } } + + @override + Future> printStruct( + Printer printer, + List printData, + ) async { + try { + log(printer.toString()); + if (printer.type == 'Bluetooth') { + final connected = await connectBluetooth(printer.address); + + connected.fold( + (f) { + return left( + PrinterFailure.dynamicErrorMessage( + 'Printer cannot connect to bluetooth, Please connect in printer setting!', + ), + ); + }, + (connect) async { + if (!connect) { + FirebaseCrashlytics.instance.recordError( + 'Failed to connect to Bluetooth printer', + null, + reason: 'Failed to connect to Bluetooth print', + information: [ + 'function: printStruct(Printer printer, List printValue,)', + 'Printer: ${printer.name}', + 'macAddress: ${printer.address}', + 'in: $_logName ', + ], + ); + + return left( + PrinterFailure.dynamicErrorMessage( + 'Printer cannot connect to bluetooth, Please connect in printer setting!', + ), + ); + } + + bool printResult = await _printBluetooth(printData); + if (!printResult) { + FirebaseCrashlytics.instance.recordError( + 'Failed to print to ${printer.name}', + null, + information: [ + 'function: await printBluetooth(printData);', + 'print: $printData', + 'in: $_logName', + ], + ); + return left( + PrinterFailure.dynamicErrorMessage( + 'Failed to print to ${printer.name}', + ), + ); + } + + return right(printResult); + }, + ); + } else { + bool printResult = await _printNetwork(printer.address, printData); + + if (!printResult) { + FirebaseCrashlytics.instance.recordError( + 'Failed to connect to Network Printer', + null, + reason: 'Failed to connect to Network Printer', + information: [ + 'function: await printNetwork(printer.address, printData);', + 'Printer: ${printer.name}', + 'ipAddress: ${printer.address}', + 'print: $printData', + 'in: $_logName', + ], + ); + } + return right(printResult); + } + + return right(false); + } catch (e) { + log("Error printing struct", name: _logName, error: e); + return left(PrinterFailure.dynamicErrorMessage('Error printing struct')); + } + } + + Future _printBluetooth(List printData) async { + try { + bool isConnected = await PrintBluetoothThermal.connectionStatus; + + if (!isConnected) { + log("Not connected to Bluetooth printer", name: _logName); + return false; + } + + bool printResult = await PrintBluetoothThermal.writeBytes(printData); + if (printResult) { + log("Successfully printed via Bluetooth", name: _logName); + } else { + FirebaseCrashlytics.instance.recordError( + 'Failed to print via Bluetooth', + null, + reason: 'Failed to print via Bluetooth', + information: [ + 'function: printBluetooth(List printData)', + 'printData: $printData', + 'in: $_logName', + ], + ); + log("Failed to print via Bluetooth", name: _logName); + } + + return printResult; + } catch (e, stackTrace) { + FirebaseCrashlytics.instance.recordError( + e, + stackTrace, + reason: 'Error printing via Bluetooth', + information: [ + 'function: printBluetooth(List printData)', + 'Printer: Bluetooth printer', + 'printData: $printData', + 'in: $_logName', + ], + ); + log("Error printing via Bluetooth: $e", name: _logName); + return false; + } + } + + Future _printNetwork(String ipAddress, List printData) async { + try { + final printer = PrinterNetworkManager(ipAddress); + PosPrintResult connect = await printer.connect(); + + if (connect == PosPrintResult.success) { + PosPrintResult printing = await printer.printTicket(printData); + printer.disconnect(); + + if (printing == PosPrintResult.success) { + log("Successfully printed via Network printer: $ipAddress"); + return true; + } else { + FirebaseCrashlytics.instance.recordError( + 'Failed to print via Network printer: ${printing.msg}', + null, + reason: 'Failed to print via Network printer', + information: [ + 'function: printNetwork(String ipAddress, List printData)', + 'Printer: Network printer', + 'ipAddress: $ipAddress', + 'printData: $printData', + ], + ); + log("Failed to print via Network printer: ${printing.msg}"); + return false; + } + } else { + FirebaseCrashlytics.instance.recordError( + 'Failed to connect to Network printer: ${connect.msg}', + null, + reason: 'Failed to connectNetwork printer', + information: [ + 'function: printNetwork(String ipAddress, List printData)', + 'Printer: Network printer', + 'ipAddress: $ipAddress', + 'printData: $printData', + ], + ); + log("Failed to connect to Network printer: ${connect.msg}"); + return false; + } + } catch (e, stackTrace) { + FirebaseCrashlytics.instance.recordError( + e, + stackTrace, + reason: 'Error printing via Network', + information: [ + 'function: printNetwork(String ipAddress, List printData)', + 'Printer: Network printer', + 'ipAddress: $ipAddress', + 'printData: $printData', + ], + ); + log("Error printing via Network: $e"); + return false; + } + } } diff --git a/lib/injection.config.dart b/lib/injection.config.dart index d365183..b7c200b 100644 --- a/lib/injection.config.dart +++ b/lib/injection.config.dart @@ -180,12 +180,12 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i708.CategoryLocalDataProvider>( () => _i708.CategoryLocalDataProvider(gh<_i487.DatabaseHelper>()), ); - gh.factory<_i464.ProductLocalDataProvider>( - () => _i464.ProductLocalDataProvider(gh<_i487.DatabaseHelper>()), - ); gh.factory<_i149.PrinterLocalDataProvider>( () => _i149.PrinterLocalDataProvider(gh<_i487.DatabaseHelper>()), ); + gh.factory<_i464.ProductLocalDataProvider>( + () => _i464.ProductLocalDataProvider(gh<_i487.DatabaseHelper>()), + ); gh.factory<_i204.AuthLocalDataProvider>( () => _i204.AuthLocalDataProvider(gh<_i460.SharedPreferences>()), ); @@ -205,9 +205,6 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i489.BluetoothConnectBloc>( () => _i489.BluetoothConnectBloc(gh<_i104.IPrinterRepository>()), ); - gh.factory<_i787.PrinterFormBloc>( - () => _i787.PrinterFormBloc(gh<_i104.IPrinterRepository>()), - ); gh.factory<_i1028.PrinterLoaderBloc>( () => _i1028.PrinterLoaderBloc(gh<_i104.IPrinterRepository>()), ); @@ -297,6 +294,12 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i143.ICustomerRepository>( () => _i385.CustomerRepository(gh<_i841.CustomerRemoteDataProvider>()), ); + gh.factory<_i787.PrinterFormBloc>( + () => _i787.PrinterFormBloc( + gh<_i104.IPrinterRepository>(), + gh<_i552.IOutletRepository>(), + ), + ); gh.factory<_i94.OrderLoaderBloc>( () => _i94.OrderLoaderBloc(gh<_i299.IOrderRepository>()), ); diff --git a/lib/presentation/components/print/print_ui.dart b/lib/presentation/components/print/print_ui.dart new file mode 100644 index 0000000..a9894a2 --- /dev/null +++ b/lib/presentation/components/print/print_ui.dart @@ -0,0 +1,72 @@ +import 'package:esc_pos_utils_plus/esc_pos_utils_plus.dart'; + +import '../../../common/extension/extension.dart'; +import '../../../domain/order/order.dart'; +import '../../../domain/outlet/outlet.dart'; +import 'receipt_component_builder.dart'; + +class PrintUi { + Future> printOrder({ + required Order order, + required Outlet outlet, + required String cashierName, + int paper = 58, + }) async { + List bytes = []; + + final profile = await CapabilityProfile.load(); + final generator = Generator( + paper == 58 ? PaperSize.mm58 : PaperSize.mm80, + profile, + ); + final builder = ReceiptComponentBuilder( + generator: generator, + paperSize: 58, + ); + + bytes += generator.reset(); + + bytes += builder.header( + outletName: outlet.name, + address: outlet.address, + phoneNumber: outlet.phoneNumber, + ); + + bytes += builder.dateTime(DateTime.now()); + + bytes += builder.orderInfo( + orderNumber: order.orderNumber, + customerName: order.metadata['customer_name'] ?? 'John Doe', + cashierName: cashierName, + paymentMethod: order.payments.last.paymentMethodName, + tableNumber: order.tableNumber, + ); + + bytes += builder.orderType(order.orderType); + + bytes += builder.emptyLines(1); + + for (final item in order.orderItems) { + 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.summary( + totalItems: order.orderItems.length, + subtotal: order.subtotal.currencyFormatRpV2, + discount: order.discountAmount.currencyFormatRpV2, + total: order.totalAmount.currencyFormatRpV2, + paid: order.totalPaid.currencyFormatRpV2, + ); + + bytes += builder.footer(); + + return bytes; + } +} diff --git a/lib/presentation/components/print/receipt_component_builder.dart b/lib/presentation/components/print/receipt_component_builder.dart new file mode 100644 index 0000000..790497b --- /dev/null +++ b/lib/presentation/components/print/receipt_component_builder.dart @@ -0,0 +1,270 @@ +import 'package:esc_pos_utils_plus/esc_pos_utils_plus.dart'; +import 'package:intl/intl.dart'; + +/// Reusable component builder untuk thermal receipt printer +class ReceiptComponentBuilder { + final Generator generator; + final int paperSize; + + ReceiptComponentBuilder({required this.generator, this.paperSize = 58}); + + /// Get separator line based on paper size + String get _separator => paperSize == 80 + ? '------------------------------------------------' + : '--------------------------------'; + + /// Print text centered with custom style + List textCenter( + String text, { + bool bold = false, + PosTextSize height = PosTextSize.size1, + PosTextSize width = PosTextSize.size1, + }) { + return generator.text( + text, + styles: PosStyles( + bold: bold, + align: PosAlign.center, + height: height, + width: width, + ), + ); + } + + /// Print text aligned left + List textLeft(String text, {bool bold = false}) { + return generator.text( + text, + styles: PosStyles(bold: bold, align: PosAlign.left), + ); + } + + /// Print text aligned right + List textRight(String text, {bool bold = false}) { + return generator.text( + text, + styles: PosStyles(bold: bold, align: PosAlign.right), + ); + } + + /// Print separator line + List separator({bool bold = false}) { + return generator.text( + _separator, + styles: PosStyles(bold: bold, align: PosAlign.center), + ); + } + + /// Print row with 2 columns (label: value) + List row2Columns( + String leftText, + String rightText, { + bool bold = false, + int leftWidth = 6, + int rightWidth = 6, + }) { + return generator.row([ + PosColumn( + text: leftText, + width: leftWidth, + styles: PosStyles(align: PosAlign.left, bold: bold), + ), + PosColumn( + text: rightText, + width: rightWidth, + styles: PosStyles(align: PosAlign.right, bold: bold), + ), + ]); + } + + /// Print row with 3 columns + List row3Columns( + String leftText, + String centerText, + String rightText, { + int leftWidth = 4, + int centerWidth = 4, + int rightWidth = 4, + }) { + return generator.row([ + PosColumn( + text: leftText, + width: leftWidth, + styles: const PosStyles(align: PosAlign.left), + ), + PosColumn( + text: centerText, + width: centerWidth, + styles: const PosStyles(align: PosAlign.center), + ), + PosColumn( + text: rightText, + width: rightWidth, + styles: const PosStyles(align: PosAlign.right), + ), + ]); + } + + /// Print empty lines + List emptyLines(int count) { + return generator.emptyLines(count); + } + + /// Print feed lines + List feed(int count) { + return generator.feed(count); + } + + /// Print header (outlet info) + List header({ + required String outletName, + required String address, + required String phoneNumber, + }) { + List bytes = []; + + bytes += textCenter( + outletName, + bold: true, + height: PosTextSize.size1, + width: PosTextSize.size1, + ); + bytes += textCenter(address); + bytes += textCenter(phoneNumber); + bytes += separator(); + + return bytes; + } + + /// Print date and time + List dateTime(DateTime dateTime) { + return row2Columns( + DateFormat('dd MMM yyyy').format(dateTime), + DateFormat('HH:mm').format(dateTime), + ); + } + + /// Print order info section + List orderInfo({ + required String orderNumber, + required String customerName, + required String cashierName, + String? paymentMethod, + String? tableNumber, + }) { + List bytes = []; + + bytes += row2Columns('No. Pesanan', orderNumber); + bytes += row2Columns('Pelanggan', customerName); + bytes += row2Columns('Kasir', cashierName); + + if (paymentMethod != null) { + bytes += row2Columns( + 'Pembayaran', + paymentMethod, + leftWidth: 8, + rightWidth: 4, + ); + } + + if (tableNumber != null && tableNumber.isNotEmpty) { + bytes += row2Columns('Meja', tableNumber, leftWidth: 8, rightWidth: 4); + } + + return bytes; + } + + /// Print order type (Dine In, Take Away, etc) + List orderType(String type) { + List bytes = []; + + bytes += separator(); + bytes += textCenter(type, bold: true); + bytes += separator(); + + return bytes; + } + + /// Print single item + List orderItem({ + required String productName, + required int quantity, + required String unitPrice, + required String totalPrice, + String? variantName, + String? notes, + }) { + List bytes = []; + + final variantText = variantName != null && variantName.isNotEmpty + ? "($variantName)" + : ''; + + bytes += textLeft('$productName $variantText', bold: true); + bytes += row2Columns( + '$quantity x $unitPrice', + totalPrice, + leftWidth: 8, + rightWidth: 4, + ); + + if (notes != null && notes.isNotEmpty) { + bytes += row2Columns('Note', notes, leftWidth: 4, rightWidth: 8); + } + + bytes += emptyLines(1); + + return bytes; + } + + /// Print summary section + List summary({ + required int totalItems, + required String subtotal, + required String discount, + required String total, + required String paid, + }) { + List bytes = []; + + bytes += separator(); + bytes += row2Columns('Total Item', totalItems.toString()); + bytes += row2Columns('Subtotal', subtotal); + bytes += row2Columns('Diskon', discount); + bytes += separator(); + bytes += row2Columns('Total', total, bold: true); + bytes += row2Columns('Bayar', paid); + bytes += separator(); + + return bytes; + } + + /// Print footer (thank you message) + List footer({String message = 'Terima kasih'}) { + List bytes = []; + + bytes += emptyLines(2); + bytes += textCenter( + message, + bold: true, + height: PosTextSize.size1, + width: PosTextSize.size1, + ); + bytes += feed(paperSize == 80 ? 3 : 1); + bytes += generator.cut(); + + return bytes; + } + + /// Print QR Code + List qrCode(String data, {PosAlign align = PosAlign.center}) { + return generator.qrcode(data, align: align); + } + + /// Print barcode + // List barcode(String data, {BarcodeType type = BarcodeType.code128}) { + // return generator.barcode( + // Barcode.code128(data), + // ); + // } +} diff --git a/lib/presentation/pages/main/pages/setting/sections/printer/setting_printer_form.dart b/lib/presentation/pages/main/pages/setting/sections/printer/setting_printer_form.dart index d21b07d..aa9251b 100644 --- a/lib/presentation/pages/main/pages/setting/sections/printer/setting_printer_form.dart +++ b/lib/presentation/pages/main/pages/setting/sections/printer/setting_printer_form.dart @@ -101,6 +101,23 @@ class _SettingPrinterFormState extends State { ); }, ), + BlocListener( + listenWhen: (p, c) => p.failureOrPrintTest != c.failureOrPrintTest, + listener: (context, state) { + state.failureOrPrintTest.fold( + () => null, + (either) => either.fold( + (f) => AppFlushbar.showPrinterFailureToast(context, f), + (either) { + AppFlushbar.showSuccess( + context, + 'Printer ${widget.code.toTitleCase()} test berhasil', + ); + }, + ), + ); + }, + ), ], child: BlocBuilder( builder: (context, state) { @@ -187,6 +204,20 @@ class _SettingPrinterFormState extends State { ), SpaceWidth(12), ], + Expanded( + child: AppElevatedButton.outlined( + onPressed: () { + context.read().add( + PrinterFormEvent.printTest( + code: widget.code, + macAccdress: state.address, + ), + ); + }, + label: 'Test Print', + ), + ), + SpaceWidth(12), Expanded( child: AppElevatedButton.filled( onPressed: () { diff --git a/lib/presentation/pages/main/pages/setting/sections/printer/setting_printer_receipt.dart b/lib/presentation/pages/main/pages/setting/sections/printer/setting_printer_receipt.dart index 647ff0f..558fa0a 100644 --- a/lib/presentation/pages/main/pages/setting/sections/printer/setting_printer_receipt.dart +++ b/lib/presentation/pages/main/pages/setting/sections/printer/setting_printer_receipt.dart @@ -29,29 +29,47 @@ class _SettingPrinterReceiptState extends State { @override Widget build(BuildContext context) { - return BlocListener( - listenWhen: (p, c) => - p.failureOrDeleteSuccess != c.failureOrDeleteSuccess, - listener: (context, state) { - state.failureOrDeleteSuccess.fold( - () => null, - (either) => either.fold( - (f) => AppFlushbar.showPrinterFailureToast(context, f), - (_) { - if (context.mounted) { - context.read().add( - const PrinterLoaderEvent.getByCode('receipt'), - ); - } + return MultiBlocListener( + listeners: [ + BlocListener( + listenWhen: (p, c) => + p.failureOrDeleteSuccess != c.failureOrDeleteSuccess, + listener: (context, state) { + state.failureOrDeleteSuccess.fold( + () => null, + (either) => either.fold( + (f) => AppFlushbar.showPrinterFailureToast(context, f), + (_) { + if (context.mounted) { + context.read().add( + const PrinterLoaderEvent.getByCode('receipt'), + ); + } - AppFlushbar.showSuccess( - context, - 'Printer receipt berhasil dihapus', - ); - }, - ), - ); - }, + AppFlushbar.showSuccess( + context, + 'Printer receipt berhasil dihapus', + ); + }, + ), + ); + }, + ), + BlocListener( + listenWhen: (p, c) => p.failureOrPrintTest != c.failureOrPrintTest, + listener: (context, state) { + state.failureOrPrintTest.fold( + () => null, + (either) => either.fold( + (f) => AppFlushbar.showPrinterFailureToast(context, f), + (either) { + AppFlushbar.showSuccess(context, 'Printer test berhasil'); + }, + ), + ); + }, + ), + ], child: Material( color: AppColor.background, child: SingleChildScrollView( @@ -106,6 +124,14 @@ class _SettingPrinterReceiptState extends State { PrinterFormEvent.deleted(state.printer.id), ); }, + onTestPrint: () { + context.read().add( + PrinterFormEvent.printTest( + code: state.printer.code, + macAccdress: state.printer.address, + ), + ); + }, ), (f) => f.maybeMap( orElse: () => ErrorCard( diff --git a/pubspec.lock b/pubspec.lock index 9e83ebc..75eb6b7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -281,6 +281,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" cupertino_icons: dependency: "direct main" description: @@ -361,6 +369,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.7" + esc_pos_utils_plus: + dependency: "direct main" + description: + name: esc_pos_utils_plus + sha256: "2a22d281cb6f04600ba3ebd607ad8df03a4b2446d814007d22525bab4d50c2ff" + url: "https://pub.dev" + source: hosted + version: "2.0.4" fake_async: dependency: transitive description: @@ -462,6 +478,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.4.1" + flutter_esc_pos_network: + dependency: "direct main" + description: + name: flutter_esc_pos_network + sha256: "44ca5966c82dd7a2a703a7ff95407c6738fb1d71b7540c137783b374e62a08fd" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + flutter_esc_pos_utils: + dependency: transitive + description: + name: flutter_esc_pos_utils + sha256: "2186973896d2dac3d5ebb2e09dac963001d29cd9e683f23afe8dbd5fb4013356" + url: "https://pub.dev" + source: hosted + version: "1.0.1" flutter_gen_core: dependency: transitive description: @@ -544,6 +576,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + gbk_codec: + dependency: transitive + description: + name: gbk_codec + sha256: "3af5311fc9393115e3650ae6023862adf998051a804a08fb804f042724999f61" + url: "https://pub.dev" + source: hosted + version: "0.4.0" get_it: dependency: "direct main" description: @@ -576,6 +616,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + hex: + dependency: transitive + description: + name: hex + sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" http: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d419dc7..5008cf1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,6 +41,8 @@ dependencies: fl_chart: ^1.1.1 permission_handler: ^12.0.1 print_bluetooth_thermal: ^1.1.7 + flutter_esc_pos_network: ^1.0.3 + esc_pos_utils_plus: ^2.0.4 dev_dependencies: flutter_test: