print order

This commit is contained in:
efrilm 2025-11-07 16:29:26 +07:00
parent 4eece81eb1
commit 93560b85cb
12 changed files with 1154 additions and 49 deletions

View File

@ -0,0 +1,60 @@
import 'package:bloc/bloc.dart';
import 'package:dartz/dartz.dart' hide Order;
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart' hide Order;
import '../../../domain/order/order.dart';
import '../../../domain/printer/printer.dart';
part 'print_struck_event.dart';
part 'print_struck_state.dart';
part 'print_struck_bloc.freezed.dart';
@injectable
class PrintStruckBloc extends Bloc<PrintStruckEvent, PrintStruckState> {
final IPrinterRepository _printerRepository;
PrintStruckBloc(this._printerRepository) : super(PrintStruckState.initial()) {
on<PrintStruckEvent>(_onPrintStruckEvent);
}
Future<void> _onPrintStruckEvent(
PrintStruckEvent event,
Emitter<PrintStruckState> emit,
) {
return event.map(
order: (e) async {
Either<PrinterFailure, Unit> failureOrSuccess;
emit(state.copyWith(isPrinting: true, failureOrPrintStruck: none()));
failureOrSuccess = await _printerRepository.printStruckOrder(
order: e.order,
);
emit(
state.copyWith(
isPrinting: false,
failureOrPrintStruck: optionOf(failureOrSuccess),
),
);
},
saveOrder: (e) async {
Either<PrinterFailure, Unit> failureOrSuccess;
emit(state.copyWith(isPrinting: true, failureOrPrintStruck: none()));
failureOrSuccess = await _printerRepository.printStruckSaveOrder(
order: e.order,
);
emit(
state.copyWith(
isPrinting: false,
failureOrPrintStruck: optionOf(failureOrSuccess),
),
);
},
);
}
}

View File

@ -0,0 +1,572 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'print_struck_bloc.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
);
/// @nodoc
mixin _$PrintStruckEvent {
Order get order => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(Order order) order,
required TResult Function(Order order) saveOrder,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(Order order)? order,
TResult? Function(Order order)? saveOrder,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(Order order)? order,
TResult Function(Order order)? saveOrder,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Order value) order,
required TResult Function(_SaveOrder value) saveOrder,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Order value)? order,
TResult? Function(_SaveOrder value)? saveOrder,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Order value)? order,
TResult Function(_SaveOrder value)? saveOrder,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
/// Create a copy of PrintStruckEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$PrintStruckEventCopyWith<PrintStruckEvent> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $PrintStruckEventCopyWith<$Res> {
factory $PrintStruckEventCopyWith(
PrintStruckEvent value,
$Res Function(PrintStruckEvent) then,
) = _$PrintStruckEventCopyWithImpl<$Res, PrintStruckEvent>;
@useResult
$Res call({Order order});
$OrderCopyWith<$Res> get order;
}
/// @nodoc
class _$PrintStruckEventCopyWithImpl<$Res, $Val extends PrintStruckEvent>
implements $PrintStruckEventCopyWith<$Res> {
_$PrintStruckEventCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of PrintStruckEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({Object? order = null}) {
return _then(
_value.copyWith(
order: null == order
? _value.order
: order // ignore: cast_nullable_to_non_nullable
as Order,
)
as $Val,
);
}
/// Create a copy of PrintStruckEvent
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$OrderCopyWith<$Res> get order {
return $OrderCopyWith<$Res>(_value.order, (value) {
return _then(_value.copyWith(order: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$OrderImplCopyWith<$Res>
implements $PrintStruckEventCopyWith<$Res> {
factory _$$OrderImplCopyWith(
_$OrderImpl value,
$Res Function(_$OrderImpl) then,
) = __$$OrderImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({Order order});
@override
$OrderCopyWith<$Res> get order;
}
/// @nodoc
class __$$OrderImplCopyWithImpl<$Res>
extends _$PrintStruckEventCopyWithImpl<$Res, _$OrderImpl>
implements _$$OrderImplCopyWith<$Res> {
__$$OrderImplCopyWithImpl(
_$OrderImpl _value,
$Res Function(_$OrderImpl) _then,
) : super(_value, _then);
/// Create a copy of PrintStruckEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({Object? order = null}) {
return _then(
_$OrderImpl(
null == order
? _value.order
: order // ignore: cast_nullable_to_non_nullable
as Order,
),
);
}
}
/// @nodoc
class _$OrderImpl implements _Order {
const _$OrderImpl(this.order);
@override
final Order order;
@override
String toString() {
return 'PrintStruckEvent.order(order: $order)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$OrderImpl &&
(identical(other.order, order) || other.order == order));
}
@override
int get hashCode => Object.hash(runtimeType, order);
/// Create a copy of PrintStruckEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$OrderImplCopyWith<_$OrderImpl> get copyWith =>
__$$OrderImplCopyWithImpl<_$OrderImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(Order order) order,
required TResult Function(Order order) saveOrder,
}) {
return order(this.order);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(Order order)? order,
TResult? Function(Order order)? saveOrder,
}) {
return order?.call(this.order);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(Order order)? order,
TResult Function(Order order)? saveOrder,
required TResult orElse(),
}) {
if (order != null) {
return order(this.order);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Order value) order,
required TResult Function(_SaveOrder value) saveOrder,
}) {
return order(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Order value)? order,
TResult? Function(_SaveOrder value)? saveOrder,
}) {
return order?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Order value)? order,
TResult Function(_SaveOrder value)? saveOrder,
required TResult orElse(),
}) {
if (order != null) {
return order(this);
}
return orElse();
}
}
abstract class _Order implements PrintStruckEvent {
const factory _Order(final Order order) = _$OrderImpl;
@override
Order get order;
/// Create a copy of PrintStruckEvent
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$OrderImplCopyWith<_$OrderImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$SaveOrderImplCopyWith<$Res>
implements $PrintStruckEventCopyWith<$Res> {
factory _$$SaveOrderImplCopyWith(
_$SaveOrderImpl value,
$Res Function(_$SaveOrderImpl) then,
) = __$$SaveOrderImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({Order order});
@override
$OrderCopyWith<$Res> get order;
}
/// @nodoc
class __$$SaveOrderImplCopyWithImpl<$Res>
extends _$PrintStruckEventCopyWithImpl<$Res, _$SaveOrderImpl>
implements _$$SaveOrderImplCopyWith<$Res> {
__$$SaveOrderImplCopyWithImpl(
_$SaveOrderImpl _value,
$Res Function(_$SaveOrderImpl) _then,
) : super(_value, _then);
/// Create a copy of PrintStruckEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({Object? order = null}) {
return _then(
_$SaveOrderImpl(
null == order
? _value.order
: order // ignore: cast_nullable_to_non_nullable
as Order,
),
);
}
}
/// @nodoc
class _$SaveOrderImpl implements _SaveOrder {
const _$SaveOrderImpl(this.order);
@override
final Order order;
@override
String toString() {
return 'PrintStruckEvent.saveOrder(order: $order)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SaveOrderImpl &&
(identical(other.order, order) || other.order == order));
}
@override
int get hashCode => Object.hash(runtimeType, order);
/// Create a copy of PrintStruckEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SaveOrderImplCopyWith<_$SaveOrderImpl> get copyWith =>
__$$SaveOrderImplCopyWithImpl<_$SaveOrderImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(Order order) order,
required TResult Function(Order order) saveOrder,
}) {
return saveOrder(this.order);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(Order order)? order,
TResult? Function(Order order)? saveOrder,
}) {
return saveOrder?.call(this.order);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(Order order)? order,
TResult Function(Order order)? saveOrder,
required TResult orElse(),
}) {
if (saveOrder != null) {
return saveOrder(this.order);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Order value) order,
required TResult Function(_SaveOrder value) saveOrder,
}) {
return saveOrder(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Order value)? order,
TResult? Function(_SaveOrder value)? saveOrder,
}) {
return saveOrder?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Order value)? order,
TResult Function(_SaveOrder value)? saveOrder,
required TResult orElse(),
}) {
if (saveOrder != null) {
return saveOrder(this);
}
return orElse();
}
}
abstract class _SaveOrder implements PrintStruckEvent {
const factory _SaveOrder(final Order order) = _$SaveOrderImpl;
@override
Order get order;
/// Create a copy of PrintStruckEvent
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SaveOrderImplCopyWith<_$SaveOrderImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$PrintStruckState {
Option<Either<PrinterFailure, Unit>> get failureOrPrintStruck =>
throw _privateConstructorUsedError;
bool get isPrinting => throw _privateConstructorUsedError;
/// Create a copy of PrintStruckState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$PrintStruckStateCopyWith<PrintStruckState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $PrintStruckStateCopyWith<$Res> {
factory $PrintStruckStateCopyWith(
PrintStruckState value,
$Res Function(PrintStruckState) then,
) = _$PrintStruckStateCopyWithImpl<$Res, PrintStruckState>;
@useResult
$Res call({
Option<Either<PrinterFailure, Unit>> failureOrPrintStruck,
bool isPrinting,
});
}
/// @nodoc
class _$PrintStruckStateCopyWithImpl<$Res, $Val extends PrintStruckState>
implements $PrintStruckStateCopyWith<$Res> {
_$PrintStruckStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of PrintStruckState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({Object? failureOrPrintStruck = null, Object? isPrinting = null}) {
return _then(
_value.copyWith(
failureOrPrintStruck: null == failureOrPrintStruck
? _value.failureOrPrintStruck
: failureOrPrintStruck // ignore: cast_nullable_to_non_nullable
as Option<Either<PrinterFailure, Unit>>,
isPrinting: null == isPrinting
? _value.isPrinting
: isPrinting // ignore: cast_nullable_to_non_nullable
as bool,
)
as $Val,
);
}
}
/// @nodoc
abstract class _$$PrintStruckStateImplCopyWith<$Res>
implements $PrintStruckStateCopyWith<$Res> {
factory _$$PrintStruckStateImplCopyWith(
_$PrintStruckStateImpl value,
$Res Function(_$PrintStruckStateImpl) then,
) = __$$PrintStruckStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({
Option<Either<PrinterFailure, Unit>> failureOrPrintStruck,
bool isPrinting,
});
}
/// @nodoc
class __$$PrintStruckStateImplCopyWithImpl<$Res>
extends _$PrintStruckStateCopyWithImpl<$Res, _$PrintStruckStateImpl>
implements _$$PrintStruckStateImplCopyWith<$Res> {
__$$PrintStruckStateImplCopyWithImpl(
_$PrintStruckStateImpl _value,
$Res Function(_$PrintStruckStateImpl) _then,
) : super(_value, _then);
/// Create a copy of PrintStruckState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({Object? failureOrPrintStruck = null, Object? isPrinting = null}) {
return _then(
_$PrintStruckStateImpl(
failureOrPrintStruck: null == failureOrPrintStruck
? _value.failureOrPrintStruck
: failureOrPrintStruck // ignore: cast_nullable_to_non_nullable
as Option<Either<PrinterFailure, Unit>>,
isPrinting: null == isPrinting
? _value.isPrinting
: isPrinting // ignore: cast_nullable_to_non_nullable
as bool,
),
);
}
}
/// @nodoc
class _$PrintStruckStateImpl implements _PrintStruckState {
_$PrintStruckStateImpl({
required this.failureOrPrintStruck,
this.isPrinting = false,
});
@override
final Option<Either<PrinterFailure, Unit>> failureOrPrintStruck;
@override
@JsonKey()
final bool isPrinting;
@override
String toString() {
return 'PrintStruckState(failureOrPrintStruck: $failureOrPrintStruck, isPrinting: $isPrinting)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$PrintStruckStateImpl &&
(identical(other.failureOrPrintStruck, failureOrPrintStruck) ||
other.failureOrPrintStruck == failureOrPrintStruck) &&
(identical(other.isPrinting, isPrinting) ||
other.isPrinting == isPrinting));
}
@override
int get hashCode =>
Object.hash(runtimeType, failureOrPrintStruck, isPrinting);
/// Create a copy of PrintStruckState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$PrintStruckStateImplCopyWith<_$PrintStruckStateImpl> get copyWith =>
__$$PrintStruckStateImplCopyWithImpl<_$PrintStruckStateImpl>(
this,
_$identity,
);
}
abstract class _PrintStruckState implements PrintStruckState {
factory _PrintStruckState({
required final Option<Either<PrinterFailure, Unit>> failureOrPrintStruck,
final bool isPrinting,
}) = _$PrintStruckStateImpl;
@override
Option<Either<PrinterFailure, Unit>> get failureOrPrintStruck;
@override
bool get isPrinting;
/// Create a copy of PrintStruckState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$PrintStruckStateImplCopyWith<_$PrintStruckStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -0,0 +1,7 @@
part of 'print_struck_bloc.dart';
@freezed
class PrintStruckEvent with _$PrintStruckEvent {
const factory PrintStruckEvent.order(Order order) = _Order;
const factory PrintStruckEvent.saveOrder(Order order) = _SaveOrder;
}

View File

@ -0,0 +1,12 @@
part of 'print_struck_bloc.dart';
@freezed
class PrintStruckState with _$PrintStruckState {
factory PrintStruckState({
required Option<Either<PrinterFailure, Unit>> failureOrPrintStruck,
@Default(false) bool isPrinting,
}) = _PrintStruckState;
factory PrintStruckState.initial() =>
PrintStruckState(failureOrPrintStruck: none());
}

View File

@ -1,9 +1,10 @@
import 'package:dartz/dartz.dart';
import 'package:dartz/dartz.dart' hide Order;
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';
import '../order/order.dart';
part 'printer.freezed.dart';

View File

@ -14,4 +14,9 @@ abstract class IPrinterRepository {
Printer printer,
List<int> printValue,
);
Future<Either<PrinterFailure, Unit>> printStruckOrder({required Order order});
Future<Either<PrinterFailure, Unit>> printStruckSaveOrder({
required Order order,
});
}

View File

@ -1,20 +1,32 @@
import 'dart:developer';
import 'package:dartz/dartz.dart';
import 'package:dartz/dartz.dart' hide Order;
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:injectable/injectable.dart' hide Order;
import 'package:print_bluetooth_thermal/print_bluetooth_thermal.dart';
import '../../../domain/order/order.dart';
import '../../../domain/outlet/outlet.dart';
import '../../../domain/printer/printer.dart';
import '../../../presentation/components/print/print_ui.dart';
import '../../auth/datasources/local_data_provider.dart';
import '../../outlet/datasources/local_data_provider.dart';
import '../datasource/local_data_provider.dart';
import '../printer_dtos.dart';
@Injectable(as: IPrinterRepository)
class PrinterRepository implements IPrinterRepository {
final PrinterLocalDataProvider _localDataProvider;
final OutletLocalDatasource _outletLocalDatasource;
final AuthLocalDataProvider _authLocalDataProvider;
final _logName = 'PrinterRepository';
PrinterRepository(this._localDataProvider);
PrinterRepository(
this._localDataProvider,
this._outletLocalDatasource,
this._authLocalDataProvider,
);
@override
Future<Either<PrinterFailure, bool>> connectBluetooth(
@ -385,4 +397,355 @@ class PrinterRepository implements IPrinterRepository {
return false;
}
}
@override
Future<Either<PrinterFailure, Unit>> printStruckOrder({
required Order order,
}) async {
try {
final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser();
// Struck for customer
if (order.totalPaid > 0) {
_printReceipt(order: order, outlet: outlet, cashieName: user.name);
}
// Struck for cashier
_printCashier(order: order, outlet: outlet, cashieName: user.name);
// Struck for kitchen
_printKitchen(order: order, outlet: outlet, cashieName: user.name);
// Struck for checker
_printChecker(order: order, outlet: outlet, cashieName: user.name);
// Struck for bar if exist product bar
final itemsBar = order.orderItems.where(
(item) => item.printerType == 'bar',
);
if (itemsBar.isNotEmpty) {
_printBar(order: order, outlet: outlet, cashieName: user.name);
}
return right(unit);
} catch (e, stackTrace) {
FirebaseCrashlytics.instance.recordError(
'Print Struck Order Error: $e',
stackTrace,
reason:
'Print struck order error / Printer not setting in printer page',
information: ['Order ID: ${order.id}'],
);
log("Error printing struck order", name: _logName, error: e);
return left(
const PrinterFailure.dynamicErrorMessage('Error printing order'),
);
}
}
@override
Future<Either<PrinterFailure, Unit>> printStruckSaveOrder({
required Order order,
}) async {
try {
final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser();
// Struck for cashier
_printCashier(order: order, outlet: outlet, cashieName: user.name);
// Struck for kitchen
_printKitchen(order: order, outlet: outlet, cashieName: user.name);
// Struck for checker
_printChecker(order: order, outlet: outlet, cashieName: user.name);
// Struck for bar if exist product bar
final itemsBar = order.orderItems.where(
(item) => item.printerType == 'bar',
);
if (itemsBar.isNotEmpty) {
_printBar(order: order, outlet: outlet, cashieName: user.name);
}
return right(unit);
} catch (e, stackTrace) {
FirebaseCrashlytics.instance.recordError(
'Print Struck Save Order Error: $e',
stackTrace,
reason:
'Print struck save order error / Printer not setting in printer page',
information: ['Order ID: ${order.id}'],
);
log("Error printing struck save order", name: _logName, error: e);
return left(
const PrinterFailure.dynamicErrorMessage('Error printing save order'),
);
}
}
Future<Either<PrinterFailure, Unit>> _printReceipt({
required Order order,
required Outlet outlet,
required String cashieName,
}) async {
log('Starting to print receipt', name: _logName);
final receiptPrinter = await _localDataProvider.findPrinterByCode(
'receipt',
);
if (receiptPrinter.hasData) {
try {
final printer = receiptPrinter.data!.toDomain();
final printValue = await PrintUi().printOrder(
order: order,
outlet: outlet,
cashierName: cashieName,
);
await printStruct(printer, printValue);
log('Finished printed receipt', name: _logName);
return right(unit);
} catch (e, stackTrace) {
FirebaseCrashlytics.instance.recordError(
'Print Struck Receipt Error: $e',
stackTrace,
reason:
'Print struck receipt error / Printer not setting in printer page',
information: ['Order ID: ${order.id}'],
);
log("Error printing receipt", name: _logName, error: e);
return left(
const PrinterFailure.dynamicErrorMessage(
'Error printing receipt order',
),
);
}
} else {
FirebaseCrashlytics.instance.recordError(
'Receipt printer not found',
null,
reason:
'Receipt printer not found / Printer not setting in printer page',
information: ['Order ID: ${order.id}'],
);
return left(
const PrinterFailure.dynamicErrorMessage('Receipt printer not found'),
);
}
}
Future<Either<PrinterFailure, Unit>> _printCashier({
required Order order,
required Outlet outlet,
required String cashieName,
}) async {
log('Starting to print cashier', name: _logName);
final receiptPrinter = await _localDataProvider.findPrinterByCode(
'receipt',
);
if (receiptPrinter.hasData) {
try {
final printer = receiptPrinter.data!.toDomain();
final printValue = await PrintUi().printCashier(
order: order,
outlet: outlet,
cashierName: cashieName,
);
await printStruct(printer, printValue);
log('Finished printing cashier', name: _logName);
return right(unit);
} catch (e, stackTrace) {
FirebaseCrashlytics.instance.recordError(
'Print Struck Cashier Error: $e',
stackTrace,
reason:
'Print struck cashier error / Printer not setting in printer page',
information: ['Order ID: ${order.id}'],
);
log("Error printing cashier", name: _logName, error: e);
return left(
const PrinterFailure.dynamicErrorMessage(
'Error printing cashier order',
),
);
}
} else {
FirebaseCrashlytics.instance.recordError(
'Cashier printer not found',
null,
reason:
'Cashier printer not found / Printer not setting in printer page',
information: ['Order ID: ${order.id}'],
);
return left(
const PrinterFailure.dynamicErrorMessage('Cashier printer not found'),
);
}
}
Future<Either<PrinterFailure, Unit>> _printKitchen({
required Order order,
required Outlet outlet,
required String cashieName,
}) async {
log('Starting to print kitchen', name: _logName);
final kitchenPrinter = await _localDataProvider.findPrinterByCode(
'kitchen',
);
if (kitchenPrinter.hasData) {
try {
final printer = kitchenPrinter.data!.toDomain();
final printValue = await PrintUi().printKitchen(
order: order,
outlet: outlet,
cashierName: cashieName,
);
await printStruct(printer, printValue);
log('Finished printed kitchen', name: _logName);
return right(unit);
} catch (e, stackTrace) {
FirebaseCrashlytics.instance.recordError(
'Print Struck Kitchen Error: $e',
stackTrace,
reason:
'Print struck kitchen error / Printer not setting in printer page',
information: ['Order ID: ${order.id}'],
);
log("Error printing kitchen", name: _logName, error: e);
return left(
const PrinterFailure.dynamicErrorMessage(
'Error printing kitchen order',
),
);
}
} else {
FirebaseCrashlytics.instance.recordError(
'Kitchen printer not found',
null,
reason:
'Kitchen printer not found / Printer not setting in printer page',
information: ['Order ID: ${order.id}'],
);
return left(
const PrinterFailure.dynamicErrorMessage('Kitchen printer not found'),
);
}
}
Future<Either<PrinterFailure, Unit>> _printChecker({
required Order order,
required Outlet outlet,
required String cashieName,
}) async {
log('Starting to print checker', name: _logName);
final checkerPrinter = await _localDataProvider.findPrinterByCode(
'checker',
);
if (checkerPrinter.hasData) {
try {
final printer = checkerPrinter.data!.toDomain();
final printValue = await PrintUi().printChecker(
order: order,
outlet: outlet,
cashierName: cashieName,
);
await printStruct(printer, printValue);
log('Finished printed checker', name: _logName);
return right(unit);
} catch (e, stackTrace) {
FirebaseCrashlytics.instance.recordError(
'Print Struck Checker Error: $e',
stackTrace,
reason:
'Print struck checker error / Printer not setting in printer page',
information: ['Order ID: ${order.id}'],
);
log("Error printing checker", name: _logName, error: e);
return left(
const PrinterFailure.dynamicErrorMessage(
'Error printing checker order',
),
);
}
} else {
FirebaseCrashlytics.instance.recordError(
'Checker printer not found',
null,
reason:
'Checker printer not found / Printer not setting in printer page',
information: ['Order ID: ${order.id}'],
);
return left(
const PrinterFailure.dynamicErrorMessage('Checker printer not found'),
);
}
}
Future<Either<PrinterFailure, Unit>> _printBar({
required Order order,
required Outlet outlet,
required String cashieName,
}) async {
final barPrinter = await _localDataProvider.findPrinterByCode('bar');
if (barPrinter.hasData) {
log('Starting to print bar', name: _logName);
try {
final productBar = order.orderItems
.where((item) => item.printerType == 'bar')
.toList();
if (productBar.isNotEmpty) {
final printer = barPrinter.data!.toDomain();
final printValue = await PrintUi().printBar(
order: order,
outlet: outlet,
cashierName: cashieName,
);
await printStruct(printer, printValue);
log('Finished printed bar', name: _logName);
} else {
log('Product with printer type bar not found', name: _logName);
return right(unit);
}
return right(unit);
} catch (e, stackTrace) {
FirebaseCrashlytics.instance.recordError(
'Print Struck Bar Error: $e',
stackTrace,
reason:
'Print struck bar error / Printer not setting in printer page',
information: ['Order ID: ${order.id}'],
);
log("Error printing bar", name: _logName, error: e);
return left(
const PrinterFailure.dynamicErrorMessage('Error printing bar order'),
);
}
} else {
FirebaseCrashlytics.instance.recordError(
'Bar printer not found',
null,
reason: 'Bar printer not found / Printer not setting in printer page',
information: ['Order ID: ${order.id}'],
);
return left(
const PrinterFailure.dynamicErrorMessage('Bar printer not found'),
);
}
}
}

View File

@ -46,6 +46,8 @@ import 'package:apskel_pos_flutter_v2/application/printer/bluetooth/bluetooth_co
as _i489;
import 'package:apskel_pos_flutter_v2/application/printer/bluetooth/bluetooth_loader/bluetooth_loader_bloc.dart'
as _i903;
import 'package:apskel_pos_flutter_v2/application/printer/print_struck/print_struck_bloc.dart'
as _i21;
import 'package:apskel_pos_flutter_v2/application/printer/printer_bloc.dart'
as _i96;
import 'package:apskel_pos_flutter_v2/application/printer/printer_form/printer_form_bloc.dart'
@ -195,10 +197,14 @@ extension GetItInjectableX on _i174.GetIt {
gh.lazySingleton<_i457.ApiClient>(
() => _i457.ApiClient(gh<_i361.Dio>(), gh<_i923.Env>()),
);
gh.factory<_i104.IPrinterRepository>(
() => _i881.PrinterRepository(gh<_i149.PrinterLocalDataProvider>()),
);
gh.factory<_i923.Env>(() => _i923.ProdEnv(), registerFor: {_prod});
gh.factory<_i104.IPrinterRepository>(
() => _i881.PrinterRepository(
gh<_i149.PrinterLocalDataProvider>(),
gh<_i693.OutletLocalDatasource>(),
gh<_i204.AuthLocalDataProvider>(),
),
);
gh.factory<_i903.BluetoothLoaderBloc>(
() => _i903.BluetoothLoaderBloc(gh<_i104.IPrinterRepository>()),
);
@ -208,6 +214,9 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i1028.PrinterLoaderBloc>(
() => _i1028.PrinterLoaderBloc(gh<_i104.IPrinterRepository>()),
);
gh.factory<_i21.PrintStruckBloc>(
() => _i21.PrintStruckBloc(gh<_i104.IPrinterRepository>()),
);
gh.factory<_i360.OrderRemoteDataProvider>(
() => _i360.OrderRemoteDataProvider(gh<_i457.ApiClient>()),
);

View File

@ -11,6 +11,7 @@ import '../application/outlet/outlet_loader/outlet_loader_bloc.dart';
import '../application/payment_method/payment_method_loader/payment_method_loader_bloc.dart';
import '../application/printer/bluetooth/bluetooth_connect/bluetooth_connect_bloc.dart';
import '../application/printer/bluetooth/bluetooth_loader/bluetooth_loader_bloc.dart';
import '../application/printer/print_struck/print_struck_bloc.dart';
import '../application/printer/printer_form/printer_form_bloc.dart';
import '../application/printer/printer_loader/printer_loader_bloc.dart';
import '../application/product/product_loader/product_loader_bloc.dart';
@ -53,6 +54,7 @@ class _AppWidgetState extends State<AppWidget> {
BlocProvider(create: (context) => getIt<BluetoothConnectBloc>()),
BlocProvider(create: (context) => getIt<PrinterFormBloc>()),
BlocProvider(create: (context) => getIt<PrinterLoaderBloc>()),
BlocProvider(create: (context) => getIt<PrintStruckBloc>()),
],
child: MaterialApp.router(
debugShowCheckedModeBanner: false,

View File

@ -105,7 +105,9 @@ class PrintUi {
orderNumber: order.orderNumber,
customerName: order.metadata['customer_name'] ?? 'John Doe',
cashierName: cashierName,
paymentMethod: order.payments.last.paymentMethodName,
paymentMethod: order.payments.isEmpty
? ''
: order.payments.last.paymentMethodName,
tableNumber: order.tableNumber,
);
@ -280,4 +282,70 @@ class PrintUi {
return bytes;
}
Future<List<int>> printCashier({
required Order order,
required Outlet outlet,
required String cashierName,
int paper = 58,
}) async {
List<int> 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.isEmpty
? null
: 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(message: 'Kasir');
return bytes;
}
}

View File

@ -3,11 +3,13 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../../application/order/order_loader/order_loader_bloc.dart';
import '../../../../../application/printer/print_struck/print_struck_bloc.dart';
import '../../../../../common/theme/theme.dart';
import '../../../../../domain/order/order.dart';
import '../../../../../injection.dart';
import '../../../../components/loader/loader_with_text.dart';
import '../../../../components/spaces/space.dart';
import '../../../../components/toast/flushbar.dart';
import 'widgets/success_order_left_panel.dart';
import 'widgets/success_order_right_panel.dart';
@ -18,7 +20,21 @@ class SuccessOrderPage extends StatelessWidget implements AutoRouteWrapper {
@override
Widget build(BuildContext context) {
return Scaffold(
return BlocListener<PrintStruckBloc, PrintStruckState>(
listenWhen: (previous, current) =>
previous.failureOrPrintStruck != current.failureOrPrintStruck,
listener: (context, state) {
state.failureOrPrintStruck.fold(
() {},
(either) => either.fold(
(f) => AppFlushbar.showPrinterFailureToast(context, f),
(success) {
AppFlushbar.showSuccess(context, "Struck berhasil dicetak");
},
),
);
},
child: Scaffold(
backgroundColor: AppColor.background,
body: SafeArea(
child: BlocBuilder<OrderLoaderBloc, OrderLoaderState>(
@ -46,6 +62,7 @@ class SuccessOrderPage extends StatelessWidget implements AutoRouteWrapper {
},
),
),
),
);
}

View File

@ -1,6 +1,8 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../../../application/printer/print_struck/print_struck_bloc.dart';
import '../../../../../../common/extension/extension.dart';
import '../../../../../../common/theme/theme.dart';
import '../../../../../../domain/order/order.dart';
@ -169,22 +171,9 @@ class SuccessOrderLeftPanel extends StatelessWidget {
Expanded(
child: AppElevatedButton.filled(
onPressed: () {
// onPrintRecipt(
// context,
// order: widget.order,
// paymentMethod: widget.paymentMethod,
// nominalBayar: widget.paymentMethod == "Cash"
// ? widget.nominalBayar
// : widget.order.totalAmount ?? 0,
// kembalian: widget.nominalBayar -
// (widget.order.totalAmount ?? 0),
// productQuantity: widget.productQuantity,
// );
// onPrint(
// context,
// productQuantity: widget.productQuantity,
// order: widget.order,
// );
context.read<PrintStruckBloc>().add(
PrintStruckEvent.order(order),
);
},
label: 'Cetak Struk',
icon: Icon(Icons.print_rounded, color: AppColor.white),