Compare commits

...
9 Commits
Author SHA1 Message Date
Efril f5b0e66706 config: update version 2026-07-22 14:57:06 +07:00
Efril 1ff144a1a2 feat: sort order printer 2026-07-22 14:56:38 +07:00
Efril eb031d4c2a config: prod and dev 2026-07-09 23:08:50 +07:00
Efril 7161f523c2 feat: update api url 2026-07-03 16:04:08 +07:00
Efril d097df1592 update 2026-06-04 07:22:58 +07:00
Efril e8bbef03b0 config: update version 2026-06-04 00:50:34 +07:00
Efril b228538725 feat: implement idempotency key for critical API endpoints 2026-06-04 00:48:50 +07:00
Efril 2ab20a1150 add printer checker checks 2026-05-28 18:11:02 +07:00
Efril b9fcd79962 update version 2026-05-27 19:52:47 +07:00
37 changed files with 727 additions and 191 deletions
+17
View File
@@ -0,0 +1,17 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Dev",
"request": "launch",
"type": "dart",
"args": ["--dart-define=ENV=dev"]
},
{
"name": "Prod",
"request": "launch",
"type": "dart",
"args": ["--dart-define=ENV=prod"]
}
]
}
+4
View File
@@ -1,3 +1,7 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
# This builtInKotlin flag was added automatically by Flutter migrator
android.builtInKotlin=false
# This newDsl flag was added automatically by Flutter migrator
android.newDsl=false
@@ -3,6 +3,7 @@ import 'package:dartz/dartz.dart' hide Order;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart' hide Order; import 'package:injectable/injectable.dart' hide Order;
import 'package:uuid/uuid.dart';
import '../../../common/types/order_type.dart'; import '../../../common/types/order_type.dart';
import '../../../domain/customer/customer.dart'; import '../../../domain/customer/customer.dart';
@@ -123,10 +124,15 @@ class OrderFormBloc extends Bloc<OrderFormEvent, OrderFormState> {
addItemOrder: (e) async { addItemOrder: (e) async {
Either<OrderFailure, Order> failureOrAddItemOrder; Either<OrderFailure, Order> failureOrAddItemOrder;
// Generate new idempotency key saat user intent (tap Add Items)
// Kalau sedang retry, pakai key yang sama
final idempotencyKey = state.addItemIdempotencyKey ?? const Uuid().v4();
emit( emit(
state.copyWith( state.copyWith(
isAddingItemOrder: true, isAddingItemOrder: true,
failureOrAddItemOrder: none(), failureOrAddItemOrder: none(),
addItemIdempotencyKey: idempotencyKey,
), ),
); );
@@ -145,12 +151,16 @@ class OrderFormBloc extends Bloc<OrderFormEvent, OrderFormState> {
failureOrAddItemOrder = await _repository.addItemOrder( failureOrAddItemOrder = await _repository.addItemOrder(
id: e.orderId, id: e.orderId,
request: request, request: request,
idempotencyKey: idempotencyKey,
); );
emit( emit(
state.copyWith( state.copyWith(
isAddingItemOrder: false, isAddingItemOrder: false,
failureOrAddItemOrder: optionOf(failureOrAddItemOrder), failureOrAddItemOrder: optionOf(failureOrAddItemOrder),
// Clear key on success, keep on failure for retry
addItemIdempotencyKey:
failureOrAddItemOrder.isRight() ? null : idempotencyKey,
), ),
); );
}, },
@@ -1584,6 +1584,11 @@ mixin _$OrderFormState {
bool get isCreatingWithPayment => throw _privateConstructorUsedError; bool get isCreatingWithPayment => throw _privateConstructorUsedError;
bool get isAddingItemOrder => throw _privateConstructorUsedError; bool get isAddingItemOrder => throw _privateConstructorUsedError;
/// Idempotency key untuk add item order.
/// Di-generate saat user tap "Add Items", dipakai ulang saat retry,
/// di-clear setelah sukses.
String? get addItemIdempotencyKey => throw _privateConstructorUsedError;
/// Create a copy of OrderFormState /// Create a copy of OrderFormState
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@@ -1608,6 +1613,7 @@ abstract class $OrderFormStateCopyWith<$Res> {
bool isCreating, bool isCreating,
bool isCreatingWithPayment, bool isCreatingWithPayment,
bool isAddingItemOrder, bool isAddingItemOrder,
String? addItemIdempotencyKey,
}); });
$PaymentMethodCopyWith<$Res>? get paymentMethod; $PaymentMethodCopyWith<$Res>? get paymentMethod;
@@ -1638,6 +1644,7 @@ class _$OrderFormStateCopyWithImpl<$Res, $Val extends OrderFormState>
Object? isCreating = null, Object? isCreating = null,
Object? isCreatingWithPayment = null, Object? isCreatingWithPayment = null,
Object? isAddingItemOrder = null, Object? isAddingItemOrder = null,
Object? addItemIdempotencyKey = freezed,
}) { }) {
return _then( return _then(
_value.copyWith( _value.copyWith(
@@ -1678,6 +1685,10 @@ class _$OrderFormStateCopyWithImpl<$Res, $Val extends OrderFormState>
? _value.isAddingItemOrder ? _value.isAddingItemOrder
: isAddingItemOrder // ignore: cast_nullable_to_non_nullable : isAddingItemOrder // ignore: cast_nullable_to_non_nullable
as bool, as bool,
addItemIdempotencyKey: freezed == addItemIdempotencyKey
? _value.addItemIdempotencyKey
: addItemIdempotencyKey // ignore: cast_nullable_to_non_nullable
as String?,
) )
as $Val, as $Val,
); );
@@ -1731,6 +1742,7 @@ abstract class _$$OrderFormStateImplCopyWith<$Res>
bool isCreating, bool isCreating,
bool isCreatingWithPayment, bool isCreatingWithPayment,
bool isAddingItemOrder, bool isAddingItemOrder,
String? addItemIdempotencyKey,
}); });
@override @override
@@ -1762,6 +1774,7 @@ class __$$OrderFormStateImplCopyWithImpl<$Res>
Object? isCreating = null, Object? isCreating = null,
Object? isCreatingWithPayment = null, Object? isCreatingWithPayment = null,
Object? isAddingItemOrder = null, Object? isAddingItemOrder = null,
Object? addItemIdempotencyKey = freezed,
}) { }) {
return _then( return _then(
_$OrderFormStateImpl( _$OrderFormStateImpl(
@@ -1801,6 +1814,10 @@ class __$$OrderFormStateImplCopyWithImpl<$Res>
? _value.isAddingItemOrder ? _value.isAddingItemOrder
: isAddingItemOrder // ignore: cast_nullable_to_non_nullable : isAddingItemOrder // ignore: cast_nullable_to_non_nullable
as bool, as bool,
addItemIdempotencyKey: freezed == addItemIdempotencyKey
? _value.addItemIdempotencyKey
: addItemIdempotencyKey // ignore: cast_nullable_to_non_nullable
as String?,
), ),
); );
} }
@@ -1821,6 +1838,7 @@ class _$OrderFormStateImpl
this.isCreating = false, this.isCreating = false,
this.isCreatingWithPayment = false, this.isCreatingWithPayment = false,
this.isAddingItemOrder = false, this.isAddingItemOrder = false,
this.addItemIdempotencyKey,
}); });
@override @override
@@ -1845,9 +1863,15 @@ class _$OrderFormStateImpl
@JsonKey() @JsonKey()
final bool isAddingItemOrder; final bool isAddingItemOrder;
/// Idempotency key untuk add item order.
/// Di-generate saat user tap "Add Items", dipakai ulang saat retry,
/// di-clear setelah sukses.
@override
final String? addItemIdempotencyKey;
@override @override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'OrderFormState(paymentMethod: $paymentMethod, customerName: $customerName, customer: $customer, failureOrCreateOrder: $failureOrCreateOrder, failureOrCreateOrderWithPayment: $failureOrCreateOrderWithPayment, failureOrAddItemOrder: $failureOrAddItemOrder, isCreating: $isCreating, isCreatingWithPayment: $isCreatingWithPayment, isAddingItemOrder: $isAddingItemOrder)'; return 'OrderFormState(paymentMethod: $paymentMethod, customerName: $customerName, customer: $customer, failureOrCreateOrder: $failureOrCreateOrder, failureOrCreateOrderWithPayment: $failureOrCreateOrderWithPayment, failureOrAddItemOrder: $failureOrAddItemOrder, isCreating: $isCreating, isCreatingWithPayment: $isCreatingWithPayment, isAddingItemOrder: $isAddingItemOrder, addItemIdempotencyKey: $addItemIdempotencyKey)';
} }
@override @override
@@ -1868,7 +1892,10 @@ class _$OrderFormStateImpl
..add(DiagnosticsProperty('failureOrAddItemOrder', failureOrAddItemOrder)) ..add(DiagnosticsProperty('failureOrAddItemOrder', failureOrAddItemOrder))
..add(DiagnosticsProperty('isCreating', isCreating)) ..add(DiagnosticsProperty('isCreating', isCreating))
..add(DiagnosticsProperty('isCreatingWithPayment', isCreatingWithPayment)) ..add(DiagnosticsProperty('isCreatingWithPayment', isCreatingWithPayment))
..add(DiagnosticsProperty('isAddingItemOrder', isAddingItemOrder)); ..add(DiagnosticsProperty('isAddingItemOrder', isAddingItemOrder))
..add(
DiagnosticsProperty('addItemIdempotencyKey', addItemIdempotencyKey),
);
} }
@override @override
@@ -1897,7 +1924,9 @@ class _$OrderFormStateImpl
(identical(other.isCreatingWithPayment, isCreatingWithPayment) || (identical(other.isCreatingWithPayment, isCreatingWithPayment) ||
other.isCreatingWithPayment == isCreatingWithPayment) && other.isCreatingWithPayment == isCreatingWithPayment) &&
(identical(other.isAddingItemOrder, isAddingItemOrder) || (identical(other.isAddingItemOrder, isAddingItemOrder) ||
other.isAddingItemOrder == isAddingItemOrder)); other.isAddingItemOrder == isAddingItemOrder) &&
(identical(other.addItemIdempotencyKey, addItemIdempotencyKey) ||
other.addItemIdempotencyKey == addItemIdempotencyKey));
} }
@override @override
@@ -1912,6 +1941,7 @@ class _$OrderFormStateImpl
isCreating, isCreating,
isCreatingWithPayment, isCreatingWithPayment,
isAddingItemOrder, isAddingItemOrder,
addItemIdempotencyKey,
); );
/// Create a copy of OrderFormState /// Create a copy of OrderFormState
@@ -1938,6 +1968,7 @@ abstract class _OrderFormState implements OrderFormState {
final bool isCreating, final bool isCreating,
final bool isCreatingWithPayment, final bool isCreatingWithPayment,
final bool isAddingItemOrder, final bool isAddingItemOrder,
final String? addItemIdempotencyKey,
}) = _$OrderFormStateImpl; }) = _$OrderFormStateImpl;
@override @override
@@ -1959,6 +1990,12 @@ abstract class _OrderFormState implements OrderFormState {
@override @override
bool get isAddingItemOrder; bool get isAddingItemOrder;
/// Idempotency key untuk add item order.
/// Di-generate saat user tap "Add Items", dipakai ulang saat retry,
/// di-clear setelah sukses.
@override
String? get addItemIdempotencyKey;
/// Create a copy of OrderFormState /// Create a copy of OrderFormState
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @override
@@ -13,6 +13,10 @@ class OrderFormState with _$OrderFormState {
@Default(false) bool isCreating, @Default(false) bool isCreating,
@Default(false) bool isCreatingWithPayment, @Default(false) bool isCreatingWithPayment,
@Default(false) bool isAddingItemOrder, @Default(false) bool isAddingItemOrder,
/// Idempotency key untuk add item order.
/// Di-generate saat user tap "Add Items", dipakai ulang saat retry,
/// di-clear setelah sukses.
String? addItemIdempotencyKey,
}) = _OrderFormState; }) = _OrderFormState;
factory OrderFormState.initial() => OrderFormState( factory OrderFormState.initial() => OrderFormState(
@@ -4,6 +4,7 @@ import 'package:bloc/bloc.dart';
import 'package:dartz/dartz.dart' hide Order; import 'package:dartz/dartz.dart' hide Order;
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart' hide Order; import 'package:injectable/injectable.dart' hide Order;
import 'package:uuid/uuid.dart';
import '../../../common/types/split_type.dart'; import '../../../common/types/split_type.dart';
import '../../../domain/order/order.dart'; import '../../../domain/order/order.dart';
@@ -38,7 +39,15 @@ class PaymentFormBloc extends Bloc<PaymentFormEvent, PaymentFormState> {
submitted: (e) async { submitted: (e) async {
Either<OrderFailure, Payment> failureOrPayment; Either<OrderFailure, Payment> failureOrPayment;
emit(state.copyWith(isSubmitting: true, failureOrPayment: none())); // Generate new idempotency key saat user intent (tap Bayar)
// Kalau sedang retry (isSubmitting sebelumnya gagal), pakai key yang sama
final idempotencyKey = state.idempotencyKey ?? const Uuid().v4();
emit(state.copyWith(
isSubmitting: true,
failureOrPayment: none(),
idempotencyKey: idempotencyKey,
));
final request = PaymentRequest( final request = PaymentRequest(
orderId: state.order.id, orderId: state.order.id,
@@ -58,20 +67,32 @@ class PaymentFormBloc extends Bloc<PaymentFormEvent, PaymentFormState> {
.toList(), .toList(),
); );
failureOrPayment = await _repository.createPayment(request: request); failureOrPayment = await _repository.createPayment(
request: request,
idempotencyKey: idempotencyKey,
);
emit( emit(
state.copyWith( state.copyWith(
isSubmitting: false, isSubmitting: false,
failureOrPayment: optionOf(failureOrPayment), failureOrPayment: optionOf(failureOrPayment),
// Clear key on success, keep on failure for retry
idempotencyKey: failureOrPayment.isRight() ? null : idempotencyKey,
), ),
); );
}, },
submittedSplitBill: (e) async { submittedSplitBill: (e) async {
Either<OrderFailure, Payment> failureOrPayment; Either<OrderFailure, Payment> failureOrPayment;
// Generate new idempotency key untuk split bill
final idempotencyKey = state.splitBillIdempotencyKey ?? const Uuid().v4();
emit( emit(
state.copyWith(isSubmitting: true, failureOrPaymentSplitBill: none()), state.copyWith(
isSubmitting: true,
failureOrPaymentSplitBill: none(),
splitBillIdempotencyKey: idempotencyKey,
),
); );
final request = PaymentSplitBillRequest( final request = PaymentSplitBillRequest(
@@ -93,12 +114,18 @@ class PaymentFormBloc extends Bloc<PaymentFormEvent, PaymentFormState> {
log(request.toString()); log(request.toString());
failureOrPayment = await _repository.createSplitBill(request); failureOrPayment = await _repository.createSplitBill(
request,
idempotencyKey: idempotencyKey,
);
emit( emit(
state.copyWith( state.copyWith(
isSubmitting: false, isSubmitting: false,
failureOrPaymentSplitBill: optionOf(failureOrPayment), failureOrPaymentSplitBill: optionOf(failureOrPayment),
// Clear key on success, keep on failure for retry
splitBillIdempotencyKey:
failureOrPayment.isRight() ? null : idempotencyKey,
), ),
); );
}, },
@@ -813,6 +813,14 @@ mixin _$PaymentFormState {
PaymentMethod? get paymentMethod => throw _privateConstructorUsedError; PaymentMethod? get paymentMethod => throw _privateConstructorUsedError;
bool get isSubmitting => throw _privateConstructorUsedError; bool get isSubmitting => throw _privateConstructorUsedError;
/// Idempotency key untuk payment submission.
/// Di-generate saat user tap "Bayar", dipakai ulang saat retry,
/// di-clear setelah sukses.
String? get idempotencyKey => throw _privateConstructorUsedError;
/// Idempotency key untuk split bill submission.
String? get splitBillIdempotencyKey => throw _privateConstructorUsedError;
/// Create a copy of PaymentFormState /// Create a copy of PaymentFormState
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@@ -834,6 +842,8 @@ abstract class $PaymentFormStateCopyWith<$Res> {
Option<Either<OrderFailure, Payment>> failureOrPaymentSplitBill, Option<Either<OrderFailure, Payment>> failureOrPaymentSplitBill,
PaymentMethod? paymentMethod, PaymentMethod? paymentMethod,
bool isSubmitting, bool isSubmitting,
String? idempotencyKey,
String? splitBillIdempotencyKey,
}); });
$OrderCopyWith<$Res> get order; $OrderCopyWith<$Res> get order;
@@ -861,6 +871,8 @@ class _$PaymentFormStateCopyWithImpl<$Res, $Val extends PaymentFormState>
Object? failureOrPaymentSplitBill = null, Object? failureOrPaymentSplitBill = null,
Object? paymentMethod = freezed, Object? paymentMethod = freezed,
Object? isSubmitting = null, Object? isSubmitting = null,
Object? idempotencyKey = freezed,
Object? splitBillIdempotencyKey = freezed,
}) { }) {
return _then( return _then(
_value.copyWith( _value.copyWith(
@@ -888,6 +900,14 @@ class _$PaymentFormStateCopyWithImpl<$Res, $Val extends PaymentFormState>
? _value.isSubmitting ? _value.isSubmitting
: isSubmitting // ignore: cast_nullable_to_non_nullable : isSubmitting // ignore: cast_nullable_to_non_nullable
as bool, as bool,
idempotencyKey: freezed == idempotencyKey
? _value.idempotencyKey
: idempotencyKey // ignore: cast_nullable_to_non_nullable
as String?,
splitBillIdempotencyKey: freezed == splitBillIdempotencyKey
? _value.splitBillIdempotencyKey
: splitBillIdempotencyKey // ignore: cast_nullable_to_non_nullable
as String?,
) )
as $Val, as $Val,
); );
@@ -934,6 +954,8 @@ abstract class _$$PaymentFormStateImplCopyWith<$Res>
Option<Either<OrderFailure, Payment>> failureOrPaymentSplitBill, Option<Either<OrderFailure, Payment>> failureOrPaymentSplitBill,
PaymentMethod? paymentMethod, PaymentMethod? paymentMethod,
bool isSubmitting, bool isSubmitting,
String? idempotencyKey,
String? splitBillIdempotencyKey,
}); });
@override @override
@@ -962,6 +984,8 @@ class __$$PaymentFormStateImplCopyWithImpl<$Res>
Object? failureOrPaymentSplitBill = null, Object? failureOrPaymentSplitBill = null,
Object? paymentMethod = freezed, Object? paymentMethod = freezed,
Object? isSubmitting = null, Object? isSubmitting = null,
Object? idempotencyKey = freezed,
Object? splitBillIdempotencyKey = freezed,
}) { }) {
return _then( return _then(
_$PaymentFormStateImpl( _$PaymentFormStateImpl(
@@ -989,6 +1013,14 @@ class __$$PaymentFormStateImplCopyWithImpl<$Res>
? _value.isSubmitting ? _value.isSubmitting
: isSubmitting // ignore: cast_nullable_to_non_nullable : isSubmitting // ignore: cast_nullable_to_non_nullable
as bool, as bool,
idempotencyKey: freezed == idempotencyKey
? _value.idempotencyKey
: idempotencyKey // ignore: cast_nullable_to_non_nullable
as String?,
splitBillIdempotencyKey: freezed == splitBillIdempotencyKey
? _value.splitBillIdempotencyKey
: splitBillIdempotencyKey // ignore: cast_nullable_to_non_nullable
as String?,
), ),
); );
} }
@@ -1004,6 +1036,8 @@ class _$PaymentFormStateImpl implements _PaymentFormState {
required this.failureOrPaymentSplitBill, required this.failureOrPaymentSplitBill,
this.paymentMethod, this.paymentMethod,
this.isSubmitting = false, this.isSubmitting = false,
this.idempotencyKey,
this.splitBillIdempotencyKey,
}) : _pendingItems = pendingItems; }) : _pendingItems = pendingItems;
@override @override
@@ -1026,9 +1060,19 @@ class _$PaymentFormStateImpl implements _PaymentFormState {
@JsonKey() @JsonKey()
final bool isSubmitting; final bool isSubmitting;
/// Idempotency key untuk payment submission.
/// Di-generate saat user tap "Bayar", dipakai ulang saat retry,
/// di-clear setelah sukses.
@override
final String? idempotencyKey;
/// Idempotency key untuk split bill submission.
@override
final String? splitBillIdempotencyKey;
@override @override
String toString() { String toString() {
return 'PaymentFormState(order: $order, pendingItems: $pendingItems, failureOrPayment: $failureOrPayment, failureOrPaymentSplitBill: $failureOrPaymentSplitBill, paymentMethod: $paymentMethod, isSubmitting: $isSubmitting)'; return 'PaymentFormState(order: $order, pendingItems: $pendingItems, failureOrPayment: $failureOrPayment, failureOrPaymentSplitBill: $failureOrPaymentSplitBill, paymentMethod: $paymentMethod, isSubmitting: $isSubmitting, idempotencyKey: $idempotencyKey, splitBillIdempotencyKey: $splitBillIdempotencyKey)';
} }
@override @override
@@ -1051,7 +1095,14 @@ class _$PaymentFormStateImpl implements _PaymentFormState {
(identical(other.paymentMethod, paymentMethod) || (identical(other.paymentMethod, paymentMethod) ||
other.paymentMethod == paymentMethod) && other.paymentMethod == paymentMethod) &&
(identical(other.isSubmitting, isSubmitting) || (identical(other.isSubmitting, isSubmitting) ||
other.isSubmitting == isSubmitting)); other.isSubmitting == isSubmitting) &&
(identical(other.idempotencyKey, idempotencyKey) ||
other.idempotencyKey == idempotencyKey) &&
(identical(
other.splitBillIdempotencyKey,
splitBillIdempotencyKey,
) ||
other.splitBillIdempotencyKey == splitBillIdempotencyKey));
} }
@override @override
@@ -1063,6 +1114,8 @@ class _$PaymentFormStateImpl implements _PaymentFormState {
failureOrPaymentSplitBill, failureOrPaymentSplitBill,
paymentMethod, paymentMethod,
isSubmitting, isSubmitting,
idempotencyKey,
splitBillIdempotencyKey,
); );
/// Create a copy of PaymentFormState /// Create a copy of PaymentFormState
@@ -1086,6 +1139,8 @@ abstract class _PaymentFormState implements PaymentFormState {
failureOrPaymentSplitBill, failureOrPaymentSplitBill,
final PaymentMethod? paymentMethod, final PaymentMethod? paymentMethod,
final bool isSubmitting, final bool isSubmitting,
final String? idempotencyKey,
final String? splitBillIdempotencyKey,
}) = _$PaymentFormStateImpl; }) = _$PaymentFormStateImpl;
@override @override
@@ -1101,6 +1156,16 @@ abstract class _PaymentFormState implements PaymentFormState {
@override @override
bool get isSubmitting; bool get isSubmitting;
/// Idempotency key untuk payment submission.
/// Di-generate saat user tap "Bayar", dipakai ulang saat retry,
/// di-clear setelah sukses.
@override
String? get idempotencyKey;
/// Idempotency key untuk split bill submission.
@override
String? get splitBillIdempotencyKey;
/// Create a copy of PaymentFormState /// Create a copy of PaymentFormState
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @override
@@ -9,6 +9,12 @@ class PaymentFormState with _$PaymentFormState {
required Option<Either<OrderFailure, Payment>> failureOrPaymentSplitBill, required Option<Either<OrderFailure, Payment>> failureOrPaymentSplitBill,
PaymentMethod? paymentMethod, PaymentMethod? paymentMethod,
@Default(false) bool isSubmitting, @Default(false) bool isSubmitting,
/// Idempotency key untuk payment submission.
/// Di-generate saat user tap "Bayar", dipakai ulang saat retry,
/// di-clear setelah sukses.
String? idempotencyKey,
/// Idempotency key untuk split bill submission.
String? splitBillIdempotencyKey,
}) = _PaymentFormState; }) = _PaymentFormState;
factory PaymentFormState.initial() => PaymentFormState( factory PaymentFormState.initial() => PaymentFormState(
@@ -2,6 +2,7 @@ import 'package:bloc/bloc.dart';
import 'package:dartz/dartz.dart' hide Order; import 'package:dartz/dartz.dart' hide Order;
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart' hide Order; import 'package:injectable/injectable.dart' hide Order;
import 'package:uuid/uuid.dart';
import '../../../common/data/refund_data.dart'; import '../../../common/data/refund_data.dart';
import '../../../domain/order/order.dart'; import '../../../domain/order/order.dart';
@@ -34,7 +35,15 @@ class RefundFormBloc extends Bloc<RefundFormEvent, RefundFormState> {
submitted: (e) async { submitted: (e) async {
Either<OrderFailure, Unit>? failureOrRefund; Either<OrderFailure, Unit>? failureOrRefund;
emit(state.copyWith(isSubmitting: true, failureOrRefund: none())); // Generate new idempotency key saat user intent (tap Refund)
// Kalau sedang retry, pakai key yang sama
final idempotencyKey = state.idempotencyKey ?? const Uuid().v4();
emit(state.copyWith(
isSubmitting: true,
failureOrRefund: none(),
idempotencyKey: idempotencyKey,
));
failureOrRefund = await _repository.refundOrder( failureOrRefund = await _repository.refundOrder(
id: state.order.id, id: state.order.id,
@@ -42,12 +51,15 @@ class RefundFormBloc extends Bloc<RefundFormEvent, RefundFormState> {
? state.reason ? state.reason
: state.refundReason?.value ?? '', : state.refundReason?.value ?? '',
refundAmount: state.order.totalAmount, refundAmount: state.order.totalAmount,
idempotencyKey: idempotencyKey,
); );
emit( emit(
state.copyWith( state.copyWith(
isSubmitting: false, isSubmitting: false,
failureOrRefund: optionOf(failureOrRefund), failureOrRefund: optionOf(failureOrRefund),
// Clear key on success, keep on failure for retry
idempotencyKey: failureOrRefund.isRight() ? null : idempotencyKey,
), ),
); );
}, },
@@ -692,6 +692,11 @@ mixin _$RefundFormState {
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
bool get isSubmitting => throw _privateConstructorUsedError; bool get isSubmitting => throw _privateConstructorUsedError;
/// Idempotency key untuk refund submission.
/// Di-generate saat user tap "Refund", dipakai ulang saat retry,
/// di-clear setelah sukses.
String? get idempotencyKey => throw _privateConstructorUsedError;
/// Create a copy of RefundFormState /// Create a copy of RefundFormState
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@@ -712,6 +717,7 @@ abstract class $RefundFormStateCopyWith<$Res> {
RefundReason? refundReason, RefundReason? refundReason,
Option<Either<OrderFailure, Unit>> failureOrRefund, Option<Either<OrderFailure, Unit>> failureOrRefund,
bool isSubmitting, bool isSubmitting,
String? idempotencyKey,
}); });
$OrderCopyWith<$Res> get order; $OrderCopyWith<$Res> get order;
@@ -737,6 +743,7 @@ class _$RefundFormStateCopyWithImpl<$Res, $Val extends RefundFormState>
Object? refundReason = freezed, Object? refundReason = freezed,
Object? failureOrRefund = null, Object? failureOrRefund = null,
Object? isSubmitting = null, Object? isSubmitting = null,
Object? idempotencyKey = freezed,
}) { }) {
return _then( return _then(
_value.copyWith( _value.copyWith(
@@ -760,6 +767,10 @@ class _$RefundFormStateCopyWithImpl<$Res, $Val extends RefundFormState>
? _value.isSubmitting ? _value.isSubmitting
: isSubmitting // ignore: cast_nullable_to_non_nullable : isSubmitting // ignore: cast_nullable_to_non_nullable
as bool, as bool,
idempotencyKey: freezed == idempotencyKey
? _value.idempotencyKey
: idempotencyKey // ignore: cast_nullable_to_non_nullable
as String?,
) )
as $Val, as $Val,
); );
@@ -791,6 +802,7 @@ abstract class _$$RefundFormStateImplCopyWith<$Res>
RefundReason? refundReason, RefundReason? refundReason,
Option<Either<OrderFailure, Unit>> failureOrRefund, Option<Either<OrderFailure, Unit>> failureOrRefund,
bool isSubmitting, bool isSubmitting,
String? idempotencyKey,
}); });
@override @override
@@ -816,6 +828,7 @@ class __$$RefundFormStateImplCopyWithImpl<$Res>
Object? refundReason = freezed, Object? refundReason = freezed,
Object? failureOrRefund = null, Object? failureOrRefund = null,
Object? isSubmitting = null, Object? isSubmitting = null,
Object? idempotencyKey = freezed,
}) { }) {
return _then( return _then(
_$RefundFormStateImpl( _$RefundFormStateImpl(
@@ -839,6 +852,10 @@ class __$$RefundFormStateImplCopyWithImpl<$Res>
? _value.isSubmitting ? _value.isSubmitting
: isSubmitting // ignore: cast_nullable_to_non_nullable : isSubmitting // ignore: cast_nullable_to_non_nullable
as bool, as bool,
idempotencyKey: freezed == idempotencyKey
? _value.idempotencyKey
: idempotencyKey // ignore: cast_nullable_to_non_nullable
as String?,
), ),
); );
} }
@@ -853,6 +870,7 @@ class _$RefundFormStateImpl implements _RefundFormState {
this.refundReason, this.refundReason,
required this.failureOrRefund, required this.failureOrRefund,
this.isSubmitting = false, this.isSubmitting = false,
this.idempotencyKey,
}); });
@override @override
@@ -867,9 +885,15 @@ class _$RefundFormStateImpl implements _RefundFormState {
@JsonKey() @JsonKey()
final bool isSubmitting; final bool isSubmitting;
/// Idempotency key untuk refund submission.
/// Di-generate saat user tap "Refund", dipakai ulang saat retry,
/// di-clear setelah sukses.
@override
final String? idempotencyKey;
@override @override
String toString() { String toString() {
return 'RefundFormState(order: $order, reason: $reason, refundReason: $refundReason, failureOrRefund: $failureOrRefund, isSubmitting: $isSubmitting)'; return 'RefundFormState(order: $order, reason: $reason, refundReason: $refundReason, failureOrRefund: $failureOrRefund, isSubmitting: $isSubmitting, idempotencyKey: $idempotencyKey)';
} }
@override @override
@@ -884,7 +908,9 @@ class _$RefundFormStateImpl implements _RefundFormState {
(identical(other.failureOrRefund, failureOrRefund) || (identical(other.failureOrRefund, failureOrRefund) ||
other.failureOrRefund == failureOrRefund) && other.failureOrRefund == failureOrRefund) &&
(identical(other.isSubmitting, isSubmitting) || (identical(other.isSubmitting, isSubmitting) ||
other.isSubmitting == isSubmitting)); other.isSubmitting == isSubmitting) &&
(identical(other.idempotencyKey, idempotencyKey) ||
other.idempotencyKey == idempotencyKey));
} }
@override @override
@@ -895,6 +921,7 @@ class _$RefundFormStateImpl implements _RefundFormState {
refundReason, refundReason,
failureOrRefund, failureOrRefund,
isSubmitting, isSubmitting,
idempotencyKey,
); );
/// Create a copy of RefundFormState /// Create a copy of RefundFormState
@@ -916,6 +943,7 @@ abstract class _RefundFormState implements RefundFormState {
final RefundReason? refundReason, final RefundReason? refundReason,
required final Option<Either<OrderFailure, Unit>> failureOrRefund, required final Option<Either<OrderFailure, Unit>> failureOrRefund,
final bool isSubmitting, final bool isSubmitting,
final String? idempotencyKey,
}) = _$RefundFormStateImpl; }) = _$RefundFormStateImpl;
@override @override
@@ -929,6 +957,12 @@ abstract class _RefundFormState implements RefundFormState {
@override @override
bool get isSubmitting; bool get isSubmitting;
/// Idempotency key untuk refund submission.
/// Di-generate saat user tap "Refund", dipakai ulang saat retry,
/// di-clear setelah sukses.
@override
String? get idempotencyKey;
/// Create a copy of RefundFormState /// Create a copy of RefundFormState
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @override
@@ -8,6 +8,10 @@ class RefundFormState with _$RefundFormState {
RefundReason? refundReason, RefundReason? refundReason,
required Option<Either<OrderFailure, Unit>> failureOrRefund, required Option<Either<OrderFailure, Unit>> failureOrRefund,
@Default(false) bool isSubmitting, @Default(false) bool isSubmitting,
/// Idempotency key untuk refund submission.
/// Di-generate saat user tap "Refund", dipakai ulang saat retry,
/// di-clear setelah sukses.
String? idempotencyKey,
}) = _RefundFormState; }) = _RefundFormState;
factory RefundFormState.initial() => RefundFormState( factory RefundFormState.initial() => RefundFormState(
@@ -3,6 +3,7 @@ import 'package:collection/collection.dart';
import 'package:dartz/dartz.dart' hide Order; import 'package:dartz/dartz.dart' hide Order;
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart' hide Order; import 'package:injectable/injectable.dart' hide Order;
import 'package:uuid/uuid.dart';
import '../../../common/types/void_type.dart'; import '../../../common/types/void_type.dart';
import '../../../domain/order/order.dart'; import '../../../domain/order/order.dart';
@@ -99,18 +100,28 @@ class VoidFormBloc extends Bloc<VoidFormEvent, VoidFormState> {
updatedAt: originalItem.updatedAt, updatedAt: originalItem.updatedAt,
printerType: originalItem.printerType, printerType: originalItem.printerType,
paidQuantity: originalItem.paidQuantity, paidQuantity: originalItem.paidQuantity,
printToChecker: originalItem.printToChecker,
), ),
); );
}); });
} }
emit(state.copyWith(isSubmitting: true, failureOrVoid: none())); // Generate new idempotency key saat user intent (tap Void)
// Kalau sedang retry, pakai key yang sama
final idempotencyKey = state.idempotencyKey ?? const Uuid().v4();
emit(state.copyWith(
isSubmitting: true,
failureOrVoid: none(),
idempotencyKey: idempotencyKey,
));
failureOrVoid = await _repository.voidOrder( failureOrVoid = await _repository.voidOrder(
orderId: state.order.id, orderId: state.order.id,
reason: state.voidReason ?? '', reason: state.voidReason ?? '',
orderItems: voidItems, orderItems: voidItems,
type: state.voidType.toStringType(), type: state.voidType.toStringType(),
idempotencyKey: idempotencyKey,
); );
emit( emit(
@@ -118,6 +129,8 @@ class VoidFormBloc extends Bloc<VoidFormEvent, VoidFormState> {
isSubmitting: false, isSubmitting: false,
failureOrVoid: optionOf(failureOrVoid), failureOrVoid: optionOf(failureOrVoid),
voidItems: state.voidType.isItem ? voidItems : state.pendingItems, voidItems: state.voidType.isItem ? voidItems : state.pendingItems,
// Clear key on success, keep on failure for retry
idempotencyKey: failureOrVoid.isRight() ? null : idempotencyKey,
), ),
); );
}, },
@@ -1256,6 +1256,11 @@ mixin _$VoidFormState {
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
bool get isSubmitting => throw _privateConstructorUsedError; bool get isSubmitting => throw _privateConstructorUsedError;
/// Idempotency key untuk void submission.
/// Di-generate saat user tap "Void", dipakai ulang saat retry,
/// di-clear setelah sukses atau clearState.
String? get idempotencyKey => throw _privateConstructorUsedError;
/// Create a copy of VoidFormState /// Create a copy of VoidFormState
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@@ -1280,6 +1285,7 @@ abstract class $VoidFormStateCopyWith<$Res> {
int totalPriceVoid, int totalPriceVoid,
Option<Either<OrderFailure, Unit>> failureOrVoid, Option<Either<OrderFailure, Unit>> failureOrVoid,
bool isSubmitting, bool isSubmitting,
String? idempotencyKey,
}); });
$OrderCopyWith<$Res> get order; $OrderCopyWith<$Res> get order;
@@ -1309,6 +1315,7 @@ class _$VoidFormStateCopyWithImpl<$Res, $Val extends VoidFormState>
Object? totalPriceVoid = null, Object? totalPriceVoid = null,
Object? failureOrVoid = null, Object? failureOrVoid = null,
Object? isSubmitting = null, Object? isSubmitting = null,
Object? idempotencyKey = freezed,
}) { }) {
return _then( return _then(
_value.copyWith( _value.copyWith(
@@ -1348,6 +1355,10 @@ class _$VoidFormStateCopyWithImpl<$Res, $Val extends VoidFormState>
? _value.isSubmitting ? _value.isSubmitting
: isSubmitting // ignore: cast_nullable_to_non_nullable : isSubmitting // ignore: cast_nullable_to_non_nullable
as bool, as bool,
idempotencyKey: freezed == idempotencyKey
? _value.idempotencyKey
: idempotencyKey // ignore: cast_nullable_to_non_nullable
as String?,
) )
as $Val, as $Val,
); );
@@ -1383,6 +1394,7 @@ abstract class _$$VoidFormStateImplCopyWith<$Res>
int totalPriceVoid, int totalPriceVoid,
Option<Either<OrderFailure, Unit>> failureOrVoid, Option<Either<OrderFailure, Unit>> failureOrVoid,
bool isSubmitting, bool isSubmitting,
String? idempotencyKey,
}); });
@override @override
@@ -1412,6 +1424,7 @@ class __$$VoidFormStateImplCopyWithImpl<$Res>
Object? totalPriceVoid = null, Object? totalPriceVoid = null,
Object? failureOrVoid = null, Object? failureOrVoid = null,
Object? isSubmitting = null, Object? isSubmitting = null,
Object? idempotencyKey = freezed,
}) { }) {
return _then( return _then(
_$VoidFormStateImpl( _$VoidFormStateImpl(
@@ -1451,6 +1464,10 @@ class __$$VoidFormStateImplCopyWithImpl<$Res>
? _value.isSubmitting ? _value.isSubmitting
: isSubmitting // ignore: cast_nullable_to_non_nullable : isSubmitting // ignore: cast_nullable_to_non_nullable
as bool, as bool,
idempotencyKey: freezed == idempotencyKey
? _value.idempotencyKey
: idempotencyKey // ignore: cast_nullable_to_non_nullable
as String?,
), ),
); );
} }
@@ -1469,6 +1486,7 @@ class _$VoidFormStateImpl implements _VoidFormState {
required this.totalPriceVoid, required this.totalPriceVoid,
required this.failureOrVoid, required this.failureOrVoid,
this.isSubmitting = false, this.isSubmitting = false,
this.idempotencyKey,
}) : _pendingItems = pendingItems, }) : _pendingItems = pendingItems,
_voidItems = voidItems, _voidItems = voidItems,
_selectedItemQuantities = selectedItemQuantities; _selectedItemQuantities = selectedItemQuantities;
@@ -1512,9 +1530,15 @@ class _$VoidFormStateImpl implements _VoidFormState {
@JsonKey() @JsonKey()
final bool isSubmitting; final bool isSubmitting;
/// Idempotency key untuk void submission.
/// Di-generate saat user tap "Void", dipakai ulang saat retry,
/// di-clear setelah sukses atau clearState.
@override
final String? idempotencyKey;
@override @override
String toString() { String toString() {
return 'VoidFormState(order: $order, pendingItems: $pendingItems, voidItems: $voidItems, voidType: $voidType, selectedItemQuantities: $selectedItemQuantities, voidReason: $voidReason, totalPriceVoid: $totalPriceVoid, failureOrVoid: $failureOrVoid, isSubmitting: $isSubmitting)'; return 'VoidFormState(order: $order, pendingItems: $pendingItems, voidItems: $voidItems, voidType: $voidType, selectedItemQuantities: $selectedItemQuantities, voidReason: $voidReason, totalPriceVoid: $totalPriceVoid, failureOrVoid: $failureOrVoid, isSubmitting: $isSubmitting, idempotencyKey: $idempotencyKey)';
} }
@override @override
@@ -1544,7 +1568,9 @@ class _$VoidFormStateImpl implements _VoidFormState {
(identical(other.failureOrVoid, failureOrVoid) || (identical(other.failureOrVoid, failureOrVoid) ||
other.failureOrVoid == failureOrVoid) && other.failureOrVoid == failureOrVoid) &&
(identical(other.isSubmitting, isSubmitting) || (identical(other.isSubmitting, isSubmitting) ||
other.isSubmitting == isSubmitting)); other.isSubmitting == isSubmitting) &&
(identical(other.idempotencyKey, idempotencyKey) ||
other.idempotencyKey == idempotencyKey));
} }
@override @override
@@ -1559,6 +1585,7 @@ class _$VoidFormStateImpl implements _VoidFormState {
totalPriceVoid, totalPriceVoid,
failureOrVoid, failureOrVoid,
isSubmitting, isSubmitting,
idempotencyKey,
); );
/// Create a copy of VoidFormState /// Create a copy of VoidFormState
@@ -1581,6 +1608,7 @@ abstract class _VoidFormState implements VoidFormState {
required final int totalPriceVoid, required final int totalPriceVoid,
required final Option<Either<OrderFailure, Unit>> failureOrVoid, required final Option<Either<OrderFailure, Unit>> failureOrVoid,
final bool isSubmitting, final bool isSubmitting,
final String? idempotencyKey,
}) = _$VoidFormStateImpl; }) = _$VoidFormStateImpl;
@override @override
@@ -1602,6 +1630,12 @@ abstract class _VoidFormState implements VoidFormState {
@override @override
bool get isSubmitting; bool get isSubmitting;
/// Idempotency key untuk void submission.
/// Di-generate saat user tap "Void", dipakai ulang saat retry,
/// di-clear setelah sukses atau clearState.
@override
String? get idempotencyKey;
/// Create a copy of VoidFormState /// Create a copy of VoidFormState
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @override
@@ -12,6 +12,10 @@ class VoidFormState with _$VoidFormState {
required int totalPriceVoid, required int totalPriceVoid,
required Option<Either<OrderFailure, Unit>> failureOrVoid, required Option<Either<OrderFailure, Unit>> failureOrVoid,
@Default(false) bool isSubmitting, @Default(false) bool isSubmitting,
/// Idempotency key untuk void submission.
/// Di-generate saat user tap "Void", dipakai ulang saat retry,
/// di-clear setelah sukses atau clearState.
String? idempotencyKey,
}) = _VoidFormState; }) = _VoidFormState;
factory VoidFormState.initial() => VoidFormState( factory VoidFormState.initial() => VoidFormState(
+2
View File
@@ -16,6 +16,7 @@ import 'interceptors/bad_network_interceptor.dart';
import 'interceptors/bad_request_interceptor.dart'; import 'interceptors/bad_request_interceptor.dart';
import 'interceptors/connection_timeout_interceptor.dart'; import 'interceptors/connection_timeout_interceptor.dart';
import 'interceptors/crashlytic_interceptor.dart'; import 'interceptors/crashlytic_interceptor.dart';
import 'interceptors/idempotency_interceptor.dart';
import 'interceptors/internal_server_interceptor.dart'; import 'interceptors/internal_server_interceptor.dart';
import 'interceptors/not_found_interceptor.dart'; import 'interceptors/not_found_interceptor.dart';
import 'interceptors/unauthorized_interceptor.dart'; import 'interceptors/unauthorized_interceptor.dart';
@@ -30,6 +31,7 @@ class ApiClient {
_dio.options.connectTimeout = const Duration(seconds: 20); _dio.options.connectTimeout = const Duration(seconds: 20);
_dio.options.validateStatus = (status) => _dio.options.validateStatus = (status) =>
status != null && status >= 200 && status < 500; status != null && status >= 200 && status < 500;
_dio.interceptors.add(IdempotencyInterceptor(_dio));
_dio.interceptors.add(BadNetworkErrorInterceptor()); _dio.interceptors.add(BadNetworkErrorInterceptor());
_dio.interceptors.add(BadRequestErrorInterceptor()); _dio.interceptors.add(BadRequestErrorInterceptor());
_dio.interceptors.add(InternalServerErrorInterceptor()); _dio.interceptors.add(InternalServerErrorInterceptor());
@@ -0,0 +1,104 @@
import 'package:dio/dio.dart';
import 'package:uuid/uuid.dart';
/// Interceptor yang secara otomatis menambahkan header X-Idempotency-Key
/// untuk endpoint-endpoint kritis yang memerlukan idempotency.
///
/// Key sebaiknya di-generate di level BLoC/Cubit saat user trigger action,
/// lalu dikirim via headers. Interceptor ini hanya berfungsi sebagai fallback
/// jika key belum di-set.
///
/// Jika response 409 dengan error code `request_in_progress`, interceptor
/// akan retry setelah delay 1-2 detik.
class IdempotencyInterceptor extends Interceptor {
static const _headerKey = 'X-Idempotency-Key';
static const _replayHeader = 'X-Idempotent-Replay';
// Endpoints that require idempotency keys (exact match)
static const _idempotentPaths = [
'/api/v1/payments',
'/api/v1/orders/void',
];
// Endpoints that require idempotency keys (pattern match)
static final _idempotentPathPatterns = [
RegExp(r'/api/v1/orders/.+/add-items$'),
RegExp(r'/api/v1/orders/.+/refund$'),
RegExp(r'/api/v1/payments/.+/refund$'),
];
static const _maxRetries = 3;
static const _retryDelay = Duration(seconds: 2);
final Dio _dio;
IdempotencyInterceptor(this._dio);
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
if (options.method == 'POST' && _requiresIdempotencyKey(options.path)) {
// Gunakan key yang sudah di-set dari BLoC, atau generate baru sebagai fallback
options.headers[_headerKey] ??= const Uuid().v4();
}
handler.next(options);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
// Log jika response adalah replay dari request sebelumnya
final isReplay = response.headers.value(_replayHeader);
if (isReplay == 'true') {
// Response ini adalah hasil dari request pertama yang sudah diproses.
// Treat as success — tidak perlu perlakuan khusus.
}
handler.next(response);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) async {
final response = err.response;
final options = err.requestOptions;
if (response == null) {
handler.next(err);
return;
}
// Handle 409 Conflict with request_in_progress
if (response.statusCode == 409 &&
_isRequestInProgress(response.data) &&
_requiresIdempotencyKey(options.path)) {
final retryCount = options.extra['_idempotency_retry_count'] ?? 0;
if (retryCount < _maxRetries) {
await Future.delayed(_retryDelay);
options.extra['_idempotency_retry_count'] = retryCount + 1;
try {
final retryResponse = await _dio.fetch(options);
handler.resolve(retryResponse);
} on DioException catch (e) {
handler.next(e);
}
return;
}
}
handler.next(err);
}
bool _requiresIdempotencyKey(String path) {
if (_idempotentPaths.any((p) => path.endsWith(p))) return true;
if (_idempotentPathPatterns.any((r) => r.hasMatch(path))) return true;
return false;
}
bool _isRequestInProgress(dynamic data) {
if (data is Map<String, dynamic>) {
final errorCode = data['error_code'] ?? data['code'] ?? '';
return errorCode == 'request_in_progress';
}
return false;
}
}
+9 -2
View File
@@ -17,7 +17,7 @@ class DatabaseHelper {
return await openDatabase( return await openDatabase(
path, path,
version: 1, // Updated version for categories table version: 2,
onCreate: _onCreate, onCreate: _onCreate,
onUpgrade: _onUpgrade, onUpgrade: _onUpgrade,
); );
@@ -38,6 +38,7 @@ class DatabaseHelper {
business_type TEXT, business_type TEXT,
image_url TEXT, image_url TEXT,
printer_type TEXT, printer_type TEXT,
print_to_checker INTEGER DEFAULT 0,
metadata TEXT, metadata TEXT,
is_active INTEGER, is_active INTEGER,
created_at TEXT, created_at TEXT,
@@ -107,7 +108,13 @@ class DatabaseHelper {
await db.execute('CREATE INDEX idx_printers_type ON printers(type)'); await db.execute('CREATE INDEX idx_printers_type ON printers(type)');
} }
Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {} Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
if (oldVersion < 2) {
await db.execute(
'ALTER TABLE products ADD COLUMN print_to_checker INTEGER DEFAULT 0',
);
}
}
Future<void> close() async { Future<void> close() async {
final db = await database; final db = await database;
@@ -125,6 +125,7 @@ class Order with _$Order {
createdAt: DateTime.now().subtract(const Duration(hours: 1)), createdAt: DateTime.now().subtract(const Duration(hours: 1)),
updatedAt: DateTime.now(), updatedAt: DateTime.now(),
printerType: 'Barista', printerType: 'Barista',
printToChecker: false,
paidQuantity: 2, paidQuantity: 2,
categoryId: 'CAT-001', categoryId: 'CAT-001',
categoryName: 'Minuman', categoryName: 'Minuman',
@@ -145,6 +146,7 @@ class Order with _$Order {
createdAt: DateTime.now().subtract(const Duration(hours: 1)), createdAt: DateTime.now().subtract(const Duration(hours: 1)),
updatedAt: DateTime.now(), updatedAt: DateTime.now(),
printerType: 'Kitchen', printerType: 'Kitchen',
printToChecker: false,
paidQuantity: 1, paidQuantity: 1,
categoryId: 'CAT-002', categoryId: 'CAT-002',
categoryName: 'Makanan', categoryName: 'Makanan',
@@ -165,6 +167,7 @@ class Order with _$Order {
createdAt: DateTime.now().subtract(const Duration(hours: 1)), createdAt: DateTime.now().subtract(const Duration(hours: 1)),
updatedAt: DateTime.now(), updatedAt: DateTime.now(),
printerType: 'Kitchen', printerType: 'Kitchen',
printToChecker: false,
paidQuantity: 1, paidQuantity: 1,
categoryId: 'CAT-002', categoryId: 'CAT-002',
categoryName: 'Makanan', categoryName: 'Makanan',
@@ -230,6 +233,7 @@ class OrderItem with _$OrderItem {
required DateTime createdAt, required DateTime createdAt,
required DateTime updatedAt, required DateTime updatedAt,
required String printerType, required String printerType,
required bool printToChecker,
required int paidQuantity, required int paidQuantity,
required String categoryId, required String categoryId,
required String categoryName, required String categoryName,
@@ -251,6 +255,7 @@ class OrderItem with _$OrderItem {
createdAt: DateTime(1970), createdAt: DateTime(1970),
updatedAt: DateTime(1970), updatedAt: DateTime(1970),
printerType: '', printerType: '',
printToChecker: false,
paidQuantity: 0, paidQuantity: 0,
categoryId: '', categoryId: '',
categoryName: '', categoryName: '',
@@ -274,6 +279,7 @@ class OrderItem with _$OrderItem {
createdAt: DateTime.now(), createdAt: DateTime.now(),
updatedAt: DateTime.now(), updatedAt: DateTime.now(),
printerType: productQuantity.product.printerType, printerType: productQuantity.product.printerType,
printToChecker: productQuantity.product.printToChecker,
paidQuantity: 0, paidQuantity: 0,
categoryId: '', categoryId: '',
categoryName: '', categoryName: '',
+25 -3
View File
@@ -1002,6 +1002,7 @@ mixin _$OrderItem {
DateTime get createdAt => throw _privateConstructorUsedError; DateTime get createdAt => throw _privateConstructorUsedError;
DateTime get updatedAt => throw _privateConstructorUsedError; DateTime get updatedAt => throw _privateConstructorUsedError;
String get printerType => throw _privateConstructorUsedError; String get printerType => throw _privateConstructorUsedError;
bool get printToChecker => throw _privateConstructorUsedError;
int get paidQuantity => throw _privateConstructorUsedError; int get paidQuantity => throw _privateConstructorUsedError;
String get categoryId => throw _privateConstructorUsedError; String get categoryId => throw _privateConstructorUsedError;
String get categoryName => throw _privateConstructorUsedError; String get categoryName => throw _privateConstructorUsedError;
@@ -1034,6 +1035,7 @@ abstract class $OrderItemCopyWith<$Res> {
DateTime createdAt, DateTime createdAt,
DateTime updatedAt, DateTime updatedAt,
String printerType, String printerType,
bool printToChecker,
int paidQuantity, int paidQuantity,
String categoryId, String categoryId,
String categoryName, String categoryName,
@@ -1070,6 +1072,7 @@ class _$OrderItemCopyWithImpl<$Res, $Val extends OrderItem>
Object? createdAt = null, Object? createdAt = null,
Object? updatedAt = null, Object? updatedAt = null,
Object? printerType = null, Object? printerType = null,
Object? printToChecker = null,
Object? paidQuantity = null, Object? paidQuantity = null,
Object? categoryId = null, Object? categoryId = null,
Object? categoryName = null, Object? categoryName = null,
@@ -1136,6 +1139,10 @@ class _$OrderItemCopyWithImpl<$Res, $Val extends OrderItem>
? _value.printerType ? _value.printerType
: printerType // ignore: cast_nullable_to_non_nullable : printerType // ignore: cast_nullable_to_non_nullable
as String, as String,
printToChecker: null == printToChecker
? _value.printToChecker
: printToChecker // ignore: cast_nullable_to_non_nullable
as bool,
paidQuantity: null == paidQuantity paidQuantity: null == paidQuantity
? _value.paidQuantity ? _value.paidQuantity
: paidQuantity // ignore: cast_nullable_to_non_nullable : paidQuantity // ignore: cast_nullable_to_non_nullable
@@ -1179,6 +1186,7 @@ abstract class _$$OrderItemImplCopyWith<$Res>
DateTime createdAt, DateTime createdAt,
DateTime updatedAt, DateTime updatedAt,
String printerType, String printerType,
bool printToChecker,
int paidQuantity, int paidQuantity,
String categoryId, String categoryId,
String categoryName, String categoryName,
@@ -1214,6 +1222,7 @@ class __$$OrderItemImplCopyWithImpl<$Res>
Object? createdAt = null, Object? createdAt = null,
Object? updatedAt = null, Object? updatedAt = null,
Object? printerType = null, Object? printerType = null,
Object? printToChecker = null,
Object? paidQuantity = null, Object? paidQuantity = null,
Object? categoryId = null, Object? categoryId = null,
Object? categoryName = null, Object? categoryName = null,
@@ -1280,6 +1289,10 @@ class __$$OrderItemImplCopyWithImpl<$Res>
? _value.printerType ? _value.printerType
: printerType // ignore: cast_nullable_to_non_nullable : printerType // ignore: cast_nullable_to_non_nullable
as String, as String,
printToChecker: null == printToChecker
? _value.printToChecker
: printToChecker // ignore: cast_nullable_to_non_nullable
as bool,
paidQuantity: null == paidQuantity paidQuantity: null == paidQuantity
? _value.paidQuantity ? _value.paidQuantity
: paidQuantity // ignore: cast_nullable_to_non_nullable : paidQuantity // ignore: cast_nullable_to_non_nullable
@@ -1316,6 +1329,7 @@ class _$OrderItemImpl implements _OrderItem {
required this.createdAt, required this.createdAt,
required this.updatedAt, required this.updatedAt,
required this.printerType, required this.printerType,
required this.printToChecker,
required this.paidQuantity, required this.paidQuantity,
required this.categoryId, required this.categoryId,
required this.categoryName, required this.categoryName,
@@ -1358,6 +1372,8 @@ class _$OrderItemImpl implements _OrderItem {
@override @override
final String printerType; final String printerType;
@override @override
final bool printToChecker;
@override
final int paidQuantity; final int paidQuantity;
@override @override
final String categoryId; final String categoryId;
@@ -1366,7 +1382,7 @@ class _$OrderItemImpl implements _OrderItem {
@override @override
String toString() { String toString() {
return 'OrderItem(id: $id, orderId: $orderId, productId: $productId, productName: $productName, productVariantId: $productVariantId, productVariantName: $productVariantName, quantity: $quantity, unitPrice: $unitPrice, totalPrice: $totalPrice, modifiers: $modifiers, notes: $notes, status: $status, createdAt: $createdAt, updatedAt: $updatedAt, printerType: $printerType, paidQuantity: $paidQuantity, categoryId: $categoryId, categoryName: $categoryName)'; return 'OrderItem(id: $id, orderId: $orderId, productId: $productId, productName: $productName, productVariantId: $productVariantId, productVariantName: $productVariantName, quantity: $quantity, unitPrice: $unitPrice, totalPrice: $totalPrice, modifiers: $modifiers, notes: $notes, status: $status, createdAt: $createdAt, updatedAt: $updatedAt, printerType: $printerType, printToChecker: $printToChecker, paidQuantity: $paidQuantity, categoryId: $categoryId, categoryName: $categoryName)';
} }
@override @override
@@ -1402,6 +1418,8 @@ class _$OrderItemImpl implements _OrderItem {
other.updatedAt == updatedAt) && other.updatedAt == updatedAt) &&
(identical(other.printerType, printerType) || (identical(other.printerType, printerType) ||
other.printerType == printerType) && other.printerType == printerType) &&
(identical(other.printToChecker, printToChecker) ||
other.printToChecker == printToChecker) &&
(identical(other.paidQuantity, paidQuantity) || (identical(other.paidQuantity, paidQuantity) ||
other.paidQuantity == paidQuantity) && other.paidQuantity == paidQuantity) &&
(identical(other.categoryId, categoryId) || (identical(other.categoryId, categoryId) ||
@@ -1411,7 +1429,7 @@ class _$OrderItemImpl implements _OrderItem {
} }
@override @override
int get hashCode => Object.hash( int get hashCode => Object.hashAll([
runtimeType, runtimeType,
id, id,
orderId, orderId,
@@ -1428,10 +1446,11 @@ class _$OrderItemImpl implements _OrderItem {
createdAt, createdAt,
updatedAt, updatedAt,
printerType, printerType,
printToChecker,
paidQuantity, paidQuantity,
categoryId, categoryId,
categoryName, categoryName,
); ]);
/// Create a copy of OrderItem /// Create a copy of OrderItem
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@@ -1459,6 +1478,7 @@ abstract class _OrderItem implements OrderItem {
required final DateTime createdAt, required final DateTime createdAt,
required final DateTime updatedAt, required final DateTime updatedAt,
required final String printerType, required final String printerType,
required final bool printToChecker,
required final int paidQuantity, required final int paidQuantity,
required final String categoryId, required final String categoryId,
required final String categoryName, required final String categoryName,
@@ -1495,6 +1515,8 @@ abstract class _OrderItem implements OrderItem {
@override @override
String get printerType; String get printerType;
@override @override
bool get printToChecker;
@override
int get paidQuantity; int get paidQuantity;
@override @override
String get categoryId; String get categoryId;
@@ -24,10 +24,12 @@ abstract class IOrderRepository {
Future<Either<OrderFailure, Order>> addItemOrder({ Future<Either<OrderFailure, Order>> addItemOrder({
required String id, required String id,
required List<AddItemOrderRequest> request, required List<AddItemOrderRequest> request,
String? idempotencyKey,
}); });
Future<Either<OrderFailure, Payment>> createPayment({ Future<Either<OrderFailure, Payment>> createPayment({
required PaymentRequest request, required PaymentRequest request,
String? idempotencyKey,
}); });
Future<Either<OrderFailure, Unit>> voidOrder({ Future<Either<OrderFailure, Unit>> voidOrder({
@@ -35,15 +37,18 @@ abstract class IOrderRepository {
required String reason, required String reason,
String type = "ITEM", // TYPE: ALL, ITEM String type = "ITEM", // TYPE: ALL, ITEM
required List<OrderItem> orderItems, required List<OrderItem> orderItems,
String? idempotencyKey,
}); });
Future<Either<OrderFailure, Payment>> createSplitBill( Future<Either<OrderFailure, Payment>> createSplitBill(
PaymentSplitBillRequest request, PaymentSplitBillRequest request, {
); String? idempotencyKey,
});
Future<Either<OrderFailure, Unit>> refundOrder({ Future<Either<OrderFailure, Unit>> refundOrder({
required String id, required String id,
required String reason, required String reason,
required int refundAmount, required int refundAmount,
String? idempotencyKey,
}); });
} }
@@ -33,6 +33,7 @@ class Product with _$Product {
required String businessType, required String businessType,
required String imageUrl, required String imageUrl,
required String printerType, required String printerType,
required bool printToChecker,
required Map<String, dynamic> metadata, required Map<String, dynamic> metadata,
required bool isActive, required bool isActive,
required String createdAt, required String createdAt,
@@ -52,6 +53,7 @@ class Product with _$Product {
businessType: '', businessType: '',
imageUrl: '', imageUrl: '',
printerType: '', printerType: '',
printToChecker: false,
metadata: {}, metadata: {},
isActive: false, isActive: false,
createdAt: '', createdAt: '',
+24 -1
View File
@@ -279,6 +279,7 @@ mixin _$Product {
String get businessType => throw _privateConstructorUsedError; String get businessType => throw _privateConstructorUsedError;
String get imageUrl => throw _privateConstructorUsedError; String get imageUrl => throw _privateConstructorUsedError;
String get printerType => throw _privateConstructorUsedError; String get printerType => throw _privateConstructorUsedError;
bool get printToChecker => throw _privateConstructorUsedError;
Map<String, dynamic> get metadata => throw _privateConstructorUsedError; Map<String, dynamic> get metadata => throw _privateConstructorUsedError;
bool get isActive => throw _privateConstructorUsedError; bool get isActive => throw _privateConstructorUsedError;
String get createdAt => throw _privateConstructorUsedError; String get createdAt => throw _privateConstructorUsedError;
@@ -308,6 +309,7 @@ abstract class $ProductCopyWith<$Res> {
String businessType, String businessType,
String imageUrl, String imageUrl,
String printerType, String printerType,
bool printToChecker,
Map<String, dynamic> metadata, Map<String, dynamic> metadata,
bool isActive, bool isActive,
String createdAt, String createdAt,
@@ -342,6 +344,7 @@ class _$ProductCopyWithImpl<$Res, $Val extends Product>
Object? businessType = null, Object? businessType = null,
Object? imageUrl = null, Object? imageUrl = null,
Object? printerType = null, Object? printerType = null,
Object? printToChecker = null,
Object? metadata = null, Object? metadata = null,
Object? isActive = null, Object? isActive = null,
Object? createdAt = null, Object? createdAt = null,
@@ -394,6 +397,10 @@ class _$ProductCopyWithImpl<$Res, $Val extends Product>
? _value.printerType ? _value.printerType
: printerType // ignore: cast_nullable_to_non_nullable : printerType // ignore: cast_nullable_to_non_nullable
as String, as String,
printToChecker: null == printToChecker
? _value.printToChecker
: printToChecker // ignore: cast_nullable_to_non_nullable
as bool,
metadata: null == metadata metadata: null == metadata
? _value.metadata ? _value.metadata
: metadata // ignore: cast_nullable_to_non_nullable : metadata // ignore: cast_nullable_to_non_nullable
@@ -440,6 +447,7 @@ abstract class _$$ProductImplCopyWith<$Res> implements $ProductCopyWith<$Res> {
String businessType, String businessType,
String imageUrl, String imageUrl,
String printerType, String printerType,
bool printToChecker,
Map<String, dynamic> metadata, Map<String, dynamic> metadata,
bool isActive, bool isActive,
String createdAt, String createdAt,
@@ -473,6 +481,7 @@ class __$$ProductImplCopyWithImpl<$Res>
Object? businessType = null, Object? businessType = null,
Object? imageUrl = null, Object? imageUrl = null,
Object? printerType = null, Object? printerType = null,
Object? printToChecker = null,
Object? metadata = null, Object? metadata = null,
Object? isActive = null, Object? isActive = null,
Object? createdAt = null, Object? createdAt = null,
@@ -525,6 +534,10 @@ class __$$ProductImplCopyWithImpl<$Res>
? _value.printerType ? _value.printerType
: printerType // ignore: cast_nullable_to_non_nullable : printerType // ignore: cast_nullable_to_non_nullable
as String, as String,
printToChecker: null == printToChecker
? _value.printToChecker
: printToChecker // ignore: cast_nullable_to_non_nullable
as bool,
metadata: null == metadata metadata: null == metadata
? _value._metadata ? _value._metadata
: metadata // ignore: cast_nullable_to_non_nullable : metadata // ignore: cast_nullable_to_non_nullable
@@ -565,6 +578,7 @@ class _$ProductImpl with DiagnosticableTreeMixin implements _Product {
required this.businessType, required this.businessType,
required this.imageUrl, required this.imageUrl,
required this.printerType, required this.printerType,
required this.printToChecker,
required final Map<String, dynamic> metadata, required final Map<String, dynamic> metadata,
required this.isActive, required this.isActive,
required this.createdAt, required this.createdAt,
@@ -595,6 +609,8 @@ class _$ProductImpl with DiagnosticableTreeMixin implements _Product {
final String imageUrl; final String imageUrl;
@override @override
final String printerType; final String printerType;
@override
final bool printToChecker;
final Map<String, dynamic> _metadata; final Map<String, dynamic> _metadata;
@override @override
Map<String, dynamic> get metadata { Map<String, dynamic> get metadata {
@@ -619,7 +635,7 @@ class _$ProductImpl with DiagnosticableTreeMixin implements _Product {
@override @override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'Product(id: $id, organizationId: $organizationId, categoryId: $categoryId, sku: $sku, name: $name, description: $description, price: $price, cost: $cost, businessType: $businessType, imageUrl: $imageUrl, printerType: $printerType, metadata: $metadata, isActive: $isActive, createdAt: $createdAt, updatedAt: $updatedAt, variants: $variants)'; return 'Product(id: $id, organizationId: $organizationId, categoryId: $categoryId, sku: $sku, name: $name, description: $description, price: $price, cost: $cost, businessType: $businessType, imageUrl: $imageUrl, printerType: $printerType, printToChecker: $printToChecker, metadata: $metadata, isActive: $isActive, createdAt: $createdAt, updatedAt: $updatedAt, variants: $variants)';
} }
@override @override
@@ -638,6 +654,7 @@ class _$ProductImpl with DiagnosticableTreeMixin implements _Product {
..add(DiagnosticsProperty('businessType', businessType)) ..add(DiagnosticsProperty('businessType', businessType))
..add(DiagnosticsProperty('imageUrl', imageUrl)) ..add(DiagnosticsProperty('imageUrl', imageUrl))
..add(DiagnosticsProperty('printerType', printerType)) ..add(DiagnosticsProperty('printerType', printerType))
..add(DiagnosticsProperty('printToChecker', printToChecker))
..add(DiagnosticsProperty('metadata', metadata)) ..add(DiagnosticsProperty('metadata', metadata))
..add(DiagnosticsProperty('isActive', isActive)) ..add(DiagnosticsProperty('isActive', isActive))
..add(DiagnosticsProperty('createdAt', createdAt)) ..add(DiagnosticsProperty('createdAt', createdAt))
@@ -667,6 +684,8 @@ class _$ProductImpl with DiagnosticableTreeMixin implements _Product {
other.imageUrl == imageUrl) && other.imageUrl == imageUrl) &&
(identical(other.printerType, printerType) || (identical(other.printerType, printerType) ||
other.printerType == printerType) && other.printerType == printerType) &&
(identical(other.printToChecker, printToChecker) ||
other.printToChecker == printToChecker) &&
const DeepCollectionEquality().equals(other._metadata, _metadata) && const DeepCollectionEquality().equals(other._metadata, _metadata) &&
(identical(other.isActive, isActive) || (identical(other.isActive, isActive) ||
other.isActive == isActive) && other.isActive == isActive) &&
@@ -691,6 +710,7 @@ class _$ProductImpl with DiagnosticableTreeMixin implements _Product {
businessType, businessType,
imageUrl, imageUrl,
printerType, printerType,
printToChecker,
const DeepCollectionEquality().hash(_metadata), const DeepCollectionEquality().hash(_metadata),
isActive, isActive,
createdAt, createdAt,
@@ -720,6 +740,7 @@ abstract class _Product implements Product {
required final String businessType, required final String businessType,
required final String imageUrl, required final String imageUrl,
required final String printerType, required final String printerType,
required final bool printToChecker,
required final Map<String, dynamic> metadata, required final Map<String, dynamic> metadata,
required final bool isActive, required final bool isActive,
required final String createdAt, required final String createdAt,
@@ -750,6 +771,8 @@ abstract class _Product implements Product {
@override @override
String get printerType; String get printerType;
@override @override
bool get printToChecker;
@override
Map<String, dynamic> get metadata; Map<String, dynamic> get metadata;
@override @override
bool get isActive; bool get isActive;
+4 -4
View File
@@ -10,18 +10,18 @@ abstract class Env {
@dev @dev
class DevEnv implements Env { class DevEnv implements Env {
@override @override
String get baseUrl => 'https://api-pos.apskel.id'; String get baseUrl => 'http://194.233.78.1:4001';
@override @override
String get dbName => "apskel_pos_dev.db"; // example value String get dbName => "apskel_pos_staging.db";
} }
@Injectable(as: Env) @Injectable(as: Env)
@prod @prod
class ProdEnv implements Env { class ProdEnv implements Env {
@override @override
String get baseUrl => 'https://api-pos.apskel.id'; String get baseUrl => 'https://enaklo-pos-api.altru.id';
@override @override
String get dbName => "apskel_pos_dev.db"; String get dbName => "apskel_pos_prod.db";
} }
@@ -18,6 +18,15 @@ class OrderRemoteDataProvider {
final _logName = 'OrderRemoteDataProvider'; final _logName = 'OrderRemoteDataProvider';
OrderRemoteDataProvider(this._apiClient); OrderRemoteDataProvider(this._apiClient);
/// Helper untuk merge auth header dengan idempotency key
Map<String, dynamic> _buildHeaders({String? idempotencyKey}) {
final headers = getAuthorizationHeader();
if (idempotencyKey != null) {
headers['X-Idempotency-Key'] = idempotencyKey;
}
return headers;
}
Future<DC<OrderFailure, ListOrderDto>> fetchOrders({ Future<DC<OrderFailure, ListOrderDto>> fetchOrders({
int page = 1, int page = 1,
int limit = 10, int limit = 10,
@@ -151,13 +160,14 @@ class OrderRemoteDataProvider {
} }
Future<DC<OrderFailure, PaymentDto>> storePayment( Future<DC<OrderFailure, PaymentDto>> storePayment(
PaymentRequestDto request, PaymentRequestDto request, {
) async { String? idempotencyKey,
}) async {
try { try {
final response = await _apiClient.post( final response = await _apiClient.post(
ApiPath.payments, ApiPath.payments,
data: request.toJson(), data: request.toJson(),
headers: getAuthorizationHeader(), headers: _buildHeaders(idempotencyKey: idempotencyKey),
); );
if (response.data['success'] == false) { if (response.data['success'] == false) {
@@ -178,6 +188,7 @@ class OrderRemoteDataProvider {
Future<DC<OrderFailure, OrderDto>> addItemOrder({ Future<DC<OrderFailure, OrderDto>> addItemOrder({
required String id, required String id,
required List<AddItemOrderRequestDto> request, required List<AddItemOrderRequestDto> request,
String? idempotencyKey,
}) async { }) async {
try { try {
final response = await _apiClient.post( final response = await _apiClient.post(
@@ -186,7 +197,7 @@ class OrderRemoteDataProvider {
'notes': '', 'notes': '',
'order_items': request.map((e) => e.toRequest()).toList(), 'order_items': request.map((e) => e.toRequest()).toList(),
}, },
headers: getAuthorizationHeader(), headers: _buildHeaders(idempotencyKey: idempotencyKey),
); );
if (response.data['success'] == false) { if (response.data['success'] == false) {
@@ -209,6 +220,7 @@ class OrderRemoteDataProvider {
required String reason, required String reason,
String type = "ITEM", // TYPE: ALL, ITEM String type = "ITEM", // TYPE: ALL, ITEM
required List<OrderItemDto> orderItems, required List<OrderItemDto> orderItems,
String? idempotencyKey,
}) async { }) async {
try { try {
final response = await _apiClient.post( final response = await _apiClient.post(
@@ -223,7 +235,7 @@ class OrderRemoteDataProvider {
) )
.toList(), .toList(),
}, },
headers: getAuthorizationHeader(), headers: _buildHeaders(idempotencyKey: idempotencyKey),
); );
if (response.data['success'] == false) { if (response.data['success'] == false) {
@@ -238,14 +250,15 @@ class OrderRemoteDataProvider {
} }
Future<DC<OrderFailure, PaymentDto>> createSplitBill( Future<DC<OrderFailure, PaymentDto>> createSplitBill(
PaymentSplitBillRequestDto request, PaymentSplitBillRequestDto request, {
) async { String? idempotencyKey,
}) async {
log(request.toRequest().toString()); log(request.toRequest().toString());
try { try {
final response = await _apiClient.post( final response = await _apiClient.post(
"${ApiPath.orders}/split-bill", "${ApiPath.orders}/split-bill",
data: request.toRequest(), data: request.toRequest(),
headers: getAuthorizationHeader(), headers: _buildHeaders(idempotencyKey: idempotencyKey),
); );
if (response.data['success'] == false) { if (response.data['success'] == false) {
@@ -267,12 +280,13 @@ class OrderRemoteDataProvider {
required String id, required String id,
required String reason, required String reason,
required int refundAmount, required int refundAmount,
String? idempotencyKey,
}) async { }) async {
try { try {
final response = await _apiClient.post( final response = await _apiClient.post(
'${ApiPath.orders}/$id/refund', '${ApiPath.orders}/$id/refund',
data: {'refund_amount': refundAmount, 'reason': reason}, data: {'refund_amount': refundAmount, 'reason': reason},
headers: getAuthorizationHeader(), headers: _buildHeaders(idempotencyKey: idempotencyKey),
); );
if (response.data['success'] == false) { if (response.data['success'] == false) {
@@ -114,6 +114,7 @@ class OrderItemDto with _$OrderItemDto {
@JsonKey(name: "created_at") String? createdAt, @JsonKey(name: "created_at") String? createdAt,
@JsonKey(name: "updated_at") String? updatedAt, @JsonKey(name: "updated_at") String? updatedAt,
@JsonKey(name: "printer_type") String? printerType, @JsonKey(name: "printer_type") String? printerType,
@JsonKey(name: "print_to_checker") bool? printToChecker,
@JsonKey(name: "paid_quantity") int? paidQuantity, @JsonKey(name: "paid_quantity") int? paidQuantity,
@JsonKey(name: "category_id") String? categoryId, @JsonKey(name: "category_id") String? categoryId,
@JsonKey(name: "category_name") String? categoryName, @JsonKey(name: "category_name") String? categoryName,
@@ -139,6 +140,7 @@ class OrderItemDto with _$OrderItemDto {
createdAt: createdAt != null ? DateTime.parse(createdAt!) : DateTime(1970), createdAt: createdAt != null ? DateTime.parse(createdAt!) : DateTime(1970),
updatedAt: updatedAt != null ? DateTime.parse(updatedAt!) : DateTime(1970), updatedAt: updatedAt != null ? DateTime.parse(updatedAt!) : DateTime(1970),
printerType: printerType ?? '', printerType: printerType ?? '',
printToChecker: printToChecker ?? false,
paidQuantity: paidQuantity ?? 0, paidQuantity: paidQuantity ?? 0,
categoryId: categoryId ?? '', categoryId: categoryId ?? '',
categoryName: categoryName ?? '', categoryName: categoryName ?? '',
@@ -160,6 +162,7 @@ class OrderItemDto with _$OrderItemDto {
createdAt: orderItem.createdAt.toIso8601String(), createdAt: orderItem.createdAt.toIso8601String(),
updatedAt: orderItem.updatedAt.toIso8601String(), updatedAt: orderItem.updatedAt.toIso8601String(),
printerType: orderItem.printerType, printerType: orderItem.printerType,
printToChecker: orderItem.printToChecker,
paidQuantity: orderItem.paidQuantity, paidQuantity: orderItem.paidQuantity,
categoryId: orderItem.categoryId, categoryId: orderItem.categoryId,
categoryName: orderItem.categoryName, categoryName: orderItem.categoryName,
@@ -1169,6 +1169,8 @@ mixin _$OrderItemDto {
String? get updatedAt => throw _privateConstructorUsedError; String? get updatedAt => throw _privateConstructorUsedError;
@JsonKey(name: "printer_type") @JsonKey(name: "printer_type")
String? get printerType => throw _privateConstructorUsedError; String? get printerType => throw _privateConstructorUsedError;
@JsonKey(name: "print_to_checker")
bool? get printToChecker => throw _privateConstructorUsedError;
@JsonKey(name: "paid_quantity") @JsonKey(name: "paid_quantity")
int? get paidQuantity => throw _privateConstructorUsedError; int? get paidQuantity => throw _privateConstructorUsedError;
@JsonKey(name: "category_id") @JsonKey(name: "category_id")
@@ -1209,6 +1211,7 @@ abstract class $OrderItemDtoCopyWith<$Res> {
@JsonKey(name: "created_at") String? createdAt, @JsonKey(name: "created_at") String? createdAt,
@JsonKey(name: "updated_at") String? updatedAt, @JsonKey(name: "updated_at") String? updatedAt,
@JsonKey(name: "printer_type") String? printerType, @JsonKey(name: "printer_type") String? printerType,
@JsonKey(name: "print_to_checker") bool? printToChecker,
@JsonKey(name: "paid_quantity") int? paidQuantity, @JsonKey(name: "paid_quantity") int? paidQuantity,
@JsonKey(name: "category_id") String? categoryId, @JsonKey(name: "category_id") String? categoryId,
@JsonKey(name: "category_name") String? categoryName, @JsonKey(name: "category_name") String? categoryName,
@@ -1245,6 +1248,7 @@ class _$OrderItemDtoCopyWithImpl<$Res, $Val extends OrderItemDto>
Object? createdAt = freezed, Object? createdAt = freezed,
Object? updatedAt = freezed, Object? updatedAt = freezed,
Object? printerType = freezed, Object? printerType = freezed,
Object? printToChecker = freezed,
Object? paidQuantity = freezed, Object? paidQuantity = freezed,
Object? categoryId = freezed, Object? categoryId = freezed,
Object? categoryName = freezed, Object? categoryName = freezed,
@@ -1311,6 +1315,10 @@ class _$OrderItemDtoCopyWithImpl<$Res, $Val extends OrderItemDto>
? _value.printerType ? _value.printerType
: printerType // ignore: cast_nullable_to_non_nullable : printerType // ignore: cast_nullable_to_non_nullable
as String?, as String?,
printToChecker: freezed == printToChecker
? _value.printToChecker
: printToChecker // ignore: cast_nullable_to_non_nullable
as bool?,
paidQuantity: freezed == paidQuantity paidQuantity: freezed == paidQuantity
? _value.paidQuantity ? _value.paidQuantity
: paidQuantity // ignore: cast_nullable_to_non_nullable : paidQuantity // ignore: cast_nullable_to_non_nullable
@@ -1354,6 +1362,7 @@ abstract class _$$OrderItemDtoImplCopyWith<$Res>
@JsonKey(name: "created_at") String? createdAt, @JsonKey(name: "created_at") String? createdAt,
@JsonKey(name: "updated_at") String? updatedAt, @JsonKey(name: "updated_at") String? updatedAt,
@JsonKey(name: "printer_type") String? printerType, @JsonKey(name: "printer_type") String? printerType,
@JsonKey(name: "print_to_checker") bool? printToChecker,
@JsonKey(name: "paid_quantity") int? paidQuantity, @JsonKey(name: "paid_quantity") int? paidQuantity,
@JsonKey(name: "category_id") String? categoryId, @JsonKey(name: "category_id") String? categoryId,
@JsonKey(name: "category_name") String? categoryName, @JsonKey(name: "category_name") String? categoryName,
@@ -1389,6 +1398,7 @@ class __$$OrderItemDtoImplCopyWithImpl<$Res>
Object? createdAt = freezed, Object? createdAt = freezed,
Object? updatedAt = freezed, Object? updatedAt = freezed,
Object? printerType = freezed, Object? printerType = freezed,
Object? printToChecker = freezed,
Object? paidQuantity = freezed, Object? paidQuantity = freezed,
Object? categoryId = freezed, Object? categoryId = freezed,
Object? categoryName = freezed, Object? categoryName = freezed,
@@ -1455,6 +1465,10 @@ class __$$OrderItemDtoImplCopyWithImpl<$Res>
? _value.printerType ? _value.printerType
: printerType // ignore: cast_nullable_to_non_nullable : printerType // ignore: cast_nullable_to_non_nullable
as String?, as String?,
printToChecker: freezed == printToChecker
? _value.printToChecker
: printToChecker // ignore: cast_nullable_to_non_nullable
as bool?,
paidQuantity: freezed == paidQuantity paidQuantity: freezed == paidQuantity
? _value.paidQuantity ? _value.paidQuantity
: paidQuantity // ignore: cast_nullable_to_non_nullable : paidQuantity // ignore: cast_nullable_to_non_nullable
@@ -1491,6 +1505,7 @@ class _$OrderItemDtoImpl extends _OrderItemDto {
@JsonKey(name: "created_at") this.createdAt, @JsonKey(name: "created_at") this.createdAt,
@JsonKey(name: "updated_at") this.updatedAt, @JsonKey(name: "updated_at") this.updatedAt,
@JsonKey(name: "printer_type") this.printerType, @JsonKey(name: "printer_type") this.printerType,
@JsonKey(name: "print_to_checker") this.printToChecker,
@JsonKey(name: "paid_quantity") this.paidQuantity, @JsonKey(name: "paid_quantity") this.paidQuantity,
@JsonKey(name: "category_id") this.categoryId, @JsonKey(name: "category_id") this.categoryId,
@JsonKey(name: "category_name") this.categoryName, @JsonKey(name: "category_name") this.categoryName,
@@ -1554,6 +1569,9 @@ class _$OrderItemDtoImpl extends _OrderItemDto {
@JsonKey(name: "printer_type") @JsonKey(name: "printer_type")
final String? printerType; final String? printerType;
@override @override
@JsonKey(name: "print_to_checker")
final bool? printToChecker;
@override
@JsonKey(name: "paid_quantity") @JsonKey(name: "paid_quantity")
final int? paidQuantity; final int? paidQuantity;
@override @override
@@ -1565,7 +1583,7 @@ class _$OrderItemDtoImpl extends _OrderItemDto {
@override @override
String toString() { String toString() {
return 'OrderItemDto(id: $id, orderId: $orderId, productId: $productId, productName: $productName, productVariantId: $productVariantId, productVariantName: $productVariantName, quantity: $quantity, unitPrice: $unitPrice, totalPrice: $totalPrice, modifiers: $modifiers, notes: $notes, status: $status, createdAt: $createdAt, updatedAt: $updatedAt, printerType: $printerType, paidQuantity: $paidQuantity, categoryId: $categoryId, categoryName: $categoryName)'; return 'OrderItemDto(id: $id, orderId: $orderId, productId: $productId, productName: $productName, productVariantId: $productVariantId, productVariantName: $productVariantName, quantity: $quantity, unitPrice: $unitPrice, totalPrice: $totalPrice, modifiers: $modifiers, notes: $notes, status: $status, createdAt: $createdAt, updatedAt: $updatedAt, printerType: $printerType, printToChecker: $printToChecker, paidQuantity: $paidQuantity, categoryId: $categoryId, categoryName: $categoryName)';
} }
@override @override
@@ -1601,6 +1619,8 @@ class _$OrderItemDtoImpl extends _OrderItemDto {
other.updatedAt == updatedAt) && other.updatedAt == updatedAt) &&
(identical(other.printerType, printerType) || (identical(other.printerType, printerType) ||
other.printerType == printerType) && other.printerType == printerType) &&
(identical(other.printToChecker, printToChecker) ||
other.printToChecker == printToChecker) &&
(identical(other.paidQuantity, paidQuantity) || (identical(other.paidQuantity, paidQuantity) ||
other.paidQuantity == paidQuantity) && other.paidQuantity == paidQuantity) &&
(identical(other.categoryId, categoryId) || (identical(other.categoryId, categoryId) ||
@@ -1611,7 +1631,7 @@ class _$OrderItemDtoImpl extends _OrderItemDto {
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash( int get hashCode => Object.hashAll([
runtimeType, runtimeType,
id, id,
orderId, orderId,
@@ -1628,10 +1648,11 @@ class _$OrderItemDtoImpl extends _OrderItemDto {
createdAt, createdAt,
updatedAt, updatedAt,
printerType, printerType,
printToChecker,
paidQuantity, paidQuantity,
categoryId, categoryId,
categoryName, categoryName,
); ]);
/// Create a copy of OrderItemDto /// Create a copy of OrderItemDto
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@@ -1664,6 +1685,7 @@ abstract class _OrderItemDto extends OrderItemDto {
@JsonKey(name: "created_at") final String? createdAt, @JsonKey(name: "created_at") final String? createdAt,
@JsonKey(name: "updated_at") final String? updatedAt, @JsonKey(name: "updated_at") final String? updatedAt,
@JsonKey(name: "printer_type") final String? printerType, @JsonKey(name: "printer_type") final String? printerType,
@JsonKey(name: "print_to_checker") final bool? printToChecker,
@JsonKey(name: "paid_quantity") final int? paidQuantity, @JsonKey(name: "paid_quantity") final int? paidQuantity,
@JsonKey(name: "category_id") final String? categoryId, @JsonKey(name: "category_id") final String? categoryId,
@JsonKey(name: "category_name") final String? categoryName, @JsonKey(name: "category_name") final String? categoryName,
@@ -1719,6 +1741,9 @@ abstract class _OrderItemDto extends OrderItemDto {
@JsonKey(name: "printer_type") @JsonKey(name: "printer_type")
String? get printerType; String? get printerType;
@override @override
@JsonKey(name: "print_to_checker")
bool? get printToChecker;
@override
@JsonKey(name: "paid_quantity") @JsonKey(name: "paid_quantity")
int? get paidQuantity; int? get paidQuantity;
@override @override
@@ -109,6 +109,7 @@ _$OrderItemDtoImpl _$$OrderItemDtoImplFromJson(Map<String, dynamic> json) =>
createdAt: json['created_at'] as String?, createdAt: json['created_at'] as String?,
updatedAt: json['updated_at'] as String?, updatedAt: json['updated_at'] as String?,
printerType: json['printer_type'] as String?, printerType: json['printer_type'] as String?,
printToChecker: json['print_to_checker'] as bool?,
paidQuantity: (json['paid_quantity'] as num?)?.toInt(), paidQuantity: (json['paid_quantity'] as num?)?.toInt(),
categoryId: json['category_id'] as String?, categoryId: json['category_id'] as String?,
categoryName: json['category_name'] as String?, categoryName: json['category_name'] as String?,
@@ -131,6 +132,7 @@ Map<String, dynamic> _$$OrderItemDtoImplToJson(_$OrderItemDtoImpl instance) =>
'created_at': instance.createdAt, 'created_at': instance.createdAt,
'updated_at': instance.updatedAt, 'updated_at': instance.updatedAt,
'printer_type': instance.printerType, 'printer_type': instance.printerType,
'print_to_checker': instance.printToChecker,
'paid_quantity': instance.paidQuantity, 'paid_quantity': instance.paidQuantity,
'category_id': instance.categoryId, 'category_id': instance.categoryId,
'category_name': instance.categoryName, 'category_name': instance.categoryName,
@@ -110,6 +110,7 @@ class OrderRepository implements IOrderRepository {
Future<Either<OrderFailure, Order>> addItemOrder({ Future<Either<OrderFailure, Order>> addItemOrder({
required String id, required String id,
required List<AddItemOrderRequest> request, required List<AddItemOrderRequest> request,
String? idempotencyKey,
}) async { }) async {
try { try {
final result = await _dataProvider.addItemOrder( final result = await _dataProvider.addItemOrder(
@@ -117,6 +118,7 @@ class OrderRepository implements IOrderRepository {
request: request request: request
.map((e) => AddItemOrderRequestDto.fromDomain(e)) .map((e) => AddItemOrderRequestDto.fromDomain(e))
.toList(), .toList(),
idempotencyKey: idempotencyKey,
); );
if (result.hasError) { if (result.hasError) {
@@ -134,10 +136,12 @@ class OrderRepository implements IOrderRepository {
@override @override
Future<Either<OrderFailure, Payment>> createPayment({ Future<Either<OrderFailure, Payment>> createPayment({
required PaymentRequest request, required PaymentRequest request,
String? idempotencyKey,
}) async { }) async {
try { try {
final result = await _dataProvider.storePayment( final result = await _dataProvider.storePayment(
PaymentRequestDto.fromDomain(request), PaymentRequestDto.fromDomain(request),
idempotencyKey: idempotencyKey,
); );
if (result.hasError) { if (result.hasError) {
@@ -158,6 +162,7 @@ class OrderRepository implements IOrderRepository {
required String reason, required String reason,
String type = "ITEM", String type = "ITEM",
required List<OrderItem> orderItems, required List<OrderItem> orderItems,
String? idempotencyKey,
}) async { }) async {
try { try {
final result = await _dataProvider.voidOrder( final result = await _dataProvider.voidOrder(
@@ -165,6 +170,7 @@ class OrderRepository implements IOrderRepository {
reason: reason, reason: reason,
type: type, type: type,
orderItems: orderItems.map((e) => OrderItemDto.fromDomain(e)).toList(), orderItems: orderItems.map((e) => OrderItemDto.fromDomain(e)).toList(),
idempotencyKey: idempotencyKey,
); );
if (result.hasError) { if (result.hasError) {
@@ -180,11 +186,13 @@ class OrderRepository implements IOrderRepository {
@override @override
Future<Either<OrderFailure, Payment>> createSplitBill( Future<Either<OrderFailure, Payment>> createSplitBill(
PaymentSplitBillRequest request, PaymentSplitBillRequest request, {
) async { String? idempotencyKey,
}) async {
try { try {
final result = await _dataProvider.createSplitBill( final result = await _dataProvider.createSplitBill(
PaymentSplitBillRequestDto.fromDomain(request), PaymentSplitBillRequestDto.fromDomain(request),
idempotencyKey: idempotencyKey,
); );
if (result.hasError) { if (result.hasError) {
@@ -204,12 +212,14 @@ class OrderRepository implements IOrderRepository {
required String id, required String id,
required String reason, required String reason,
required int refundAmount, required int refundAmount,
String? idempotencyKey,
}) async { }) async {
try { try {
final result = await _dataProvider.refundPayment( final result = await _dataProvider.refundPayment(
id: id, id: id,
reason: reason, reason: reason,
refundAmount: refundAmount, refundAmount: refundAmount,
idempotencyKey: idempotencyKey,
); );
if (result.hasError) { if (result.hasError) {
return left(result.error!); return left(result.error!);
@@ -460,78 +460,78 @@ class PrinterRepository implements IPrinterRepository {
required Order order, required Order order,
}) async { }) async {
return await _globalPrintLock.synchronized(() async { return await _globalPrintLock.synchronized(() async {
try { try {
final outlet = await _outletLocalDatasource.currentOutlet(); final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser(); final user = await _authLocalDataProvider.currentUser();
// Struck for customer // Struck for customer
if (order.totalPaid > 0) { if (order.totalPaid > 0) {
final result = await _printReceipt( final result = await _printReceipt(
order: order,
outlet: outlet,
cashieName: user.name,
);
if (result.isLeft()) return result; // Jika gagal, return error
}
// Struck for cashier
final cashierResult = await _printCashier(
order: order, order: order,
outlet: outlet, outlet: outlet,
cashieName: user.name, cashieName: user.name,
); );
if (result.isLeft()) return result; // Jika gagal, return error if (cashierResult.isLeft()) return cashierResult;
}
// Struck for cashier // Struck for checker
final cashierResult = await _printCashier( final checkerResult = await _printChecker(
order: order,
outlet: outlet,
cashieName: user.name,
);
if (cashierResult.isLeft()) return cashierResult;
// Struck for bar if exist product bar
final itemsKitchen = order.orderItems.where(
(item) => item.printerType == 'kitchen',
);
if (itemsKitchen.isNotEmpty) {
final kitchenResult = await _printKitchen(
order: order, order: order,
outlet: outlet, outlet: outlet,
cashieName: user.name, cashieName: user.name,
); );
if (kitchenResult.isLeft()) return kitchenResult; if (checkerResult.isLeft()) return checkerResult;
}
// Struck for bar if exist product bar // Struck for bar if exist product bar
final itemsBar = order.orderItems.where( final itemsKitchen = order.orderItems.where(
(item) => item.printerType == 'bar', (item) => item.printerType == 'kitchen',
); );
if (itemsBar.isNotEmpty) { if (itemsKitchen.isNotEmpty) {
final barResult = await _printBar( final kitchenResult = await _printKitchen(
order: order, order: order,
outlet: outlet, outlet: outlet,
cashieName: user.name, cashieName: user.name,
);
if (kitchenResult.isLeft()) return kitchenResult;
}
// Struck for bar if exist product bar
final itemsBar = order.orderItems.where(
(item) => item.printerType == 'bar',
);
if (itemsBar.isNotEmpty) {
final barResult = await _printBar(
order: order,
outlet: outlet,
cashieName: user.name,
);
if (barResult.isLeft()) return barResult;
}
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'),
); );
if (barResult.isLeft()) return barResult;
} }
// Struck for checker
final checkerResult = await _printChecker(
order: order,
outlet: outlet,
cashieName: user.name,
);
if (checkerResult.isLeft()) return checkerResult;
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'),
);
}
}); // end _globalPrintLock.synchronized }); // end _globalPrintLock.synchronized
} }
@@ -540,69 +540,69 @@ class PrinterRepository implements IPrinterRepository {
required Order order, required Order order,
}) async { }) async {
return await _globalPrintLock.synchronized(() async { return await _globalPrintLock.synchronized(() async {
try { try {
final outlet = await _outletLocalDatasource.currentOutlet(); final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser(); final user = await _authLocalDataProvider.currentUser();
// Struck for cashier // Struck for cashier
final cashierResult = await _printCashier( final cashierResult = await _printCashier(
order: order,
outlet: outlet,
cashieName: user.name,
);
if (cashierResult.isLeft()) return cashierResult;
// Struck for kitchen
// Struck for bar if exist product bar
final itemsKitchen = order.orderItems.where(
(item) => item.printerType == 'kitchen',
);
if (itemsKitchen.isNotEmpty) {
final kitchenResult = await _printKitchen(
order: order, order: order,
outlet: outlet, outlet: outlet,
cashieName: user.name, cashieName: user.name,
); );
if (kitchenResult.isLeft()) return kitchenResult; if (cashierResult.isLeft()) return cashierResult;
}
// Struck for bar if exist product bar // Struck for checker
final itemsBar = order.orderItems.where( final checkerResult = await _printChecker(
(item) => item.printerType == 'bar',
);
if (itemsBar.isNotEmpty) {
final barResult = await _printBar(
order: order, order: order,
outlet: outlet, outlet: outlet,
cashieName: user.name, cashieName: user.name,
); );
if (barResult.isLeft()) return barResult; if (checkerResult.isLeft()) return checkerResult;
// Struck for kitchen
// Struck for bar if exist product bar
final itemsKitchen = order.orderItems.where(
(item) => item.printerType == 'kitchen',
);
if (itemsKitchen.isNotEmpty) {
final kitchenResult = await _printKitchen(
order: order,
outlet: outlet,
cashieName: user.name,
);
if (kitchenResult.isLeft()) return kitchenResult;
}
// Struck for bar if exist product bar
final itemsBar = order.orderItems.where(
(item) => item.printerType == 'bar',
);
if (itemsBar.isNotEmpty) {
final barResult = await _printBar(
order: order,
outlet: outlet,
cashieName: user.name,
);
if (barResult.isLeft()) return barResult;
}
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'),
);
} }
// Struck for checker
final checkerResult = await _printChecker(
order: order,
outlet: outlet,
cashieName: user.name,
);
if (checkerResult.isLeft()) return checkerResult;
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'),
);
}
}); // end _globalPrintLock.synchronized }); // end _globalPrintLock.synchronized
} }
@@ -611,9 +611,9 @@ class PrinterRepository implements IPrinterRepository {
required Order order, required Order order,
}) async { }) async {
return await _globalPrintLock.synchronized(() async { return await _globalPrintLock.synchronized(() async {
final outlet = await _outletLocalDatasource.currentOutlet(); final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser(); final user = await _authLocalDataProvider.currentUser();
return _printCashier(order: order, outlet: outlet, cashieName: user.name); return _printCashier(order: order, outlet: outlet, cashieName: user.name);
}); });
} }
@@ -622,9 +622,9 @@ class PrinterRepository implements IPrinterRepository {
required Order order, required Order order,
}) async { }) async {
return await _globalPrintLock.synchronized(() async { return await _globalPrintLock.synchronized(() async {
final outlet = await _outletLocalDatasource.currentOutlet(); final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser(); final user = await _authLocalDataProvider.currentUser();
return _printVoid(order: order, outlet: outlet, cashieName: user.name); return _printVoid(order: order, outlet: outlet, cashieName: user.name);
}); });
} }
@@ -633,9 +633,13 @@ class PrinterRepository implements IPrinterRepository {
required Order order, required Order order,
}) async { }) async {
return await _globalPrintLock.synchronized(() async { return await _globalPrintLock.synchronized(() async {
final outlet = await _outletLocalDatasource.currentOutlet(); final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser(); final user = await _authLocalDataProvider.currentUser();
return _printSplitBill(order: order, outlet: outlet, cashieName: user.name); return _printSplitBill(
order: order,
outlet: outlet,
cashieName: user.name,
);
}); });
} }
@@ -644,9 +648,9 @@ class PrinterRepository implements IPrinterRepository {
required Order order, required Order order,
}) async { }) async {
return await _globalPrintLock.synchronized(() async { return await _globalPrintLock.synchronized(() async {
final outlet = await _outletLocalDatasource.currentOutlet(); final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser(); final user = await _authLocalDataProvider.currentUser();
return _printReceipt(order: order, outlet: outlet, cashieName: user.name); return _printReceipt(order: order, outlet: outlet, cashieName: user.name);
}); });
} }
@@ -1075,9 +1079,9 @@ class PrinterRepository implements IPrinterRepository {
required Order order, required Order order,
}) async { }) async {
return await _globalPrintLock.synchronized(() async { return await _globalPrintLock.synchronized(() async {
final outlet = await _outletLocalDatasource.currentOutlet(); final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser(); final user = await _authLocalDataProvider.currentUser();
return _printBar(order: order, outlet: outlet, cashieName: user.name); return _printBar(order: order, outlet: outlet, cashieName: user.name);
}); });
} }
@@ -1086,9 +1090,9 @@ class PrinterRepository implements IPrinterRepository {
required Order order, required Order order,
}) async { }) async {
return await _globalPrintLock.synchronized(() async { return await _globalPrintLock.synchronized(() async {
final outlet = await _outletLocalDatasource.currentOutlet(); final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser(); final user = await _authLocalDataProvider.currentUser();
return _printChecker(order: order, outlet: outlet, cashieName: user.name); return _printChecker(order: order, outlet: outlet, cashieName: user.name);
}); });
} }
@@ -1097,9 +1101,9 @@ class PrinterRepository implements IPrinterRepository {
required Order order, required Order order,
}) async { }) async {
return await _globalPrintLock.synchronized(() async { return await _globalPrintLock.synchronized(() async {
final outlet = await _outletLocalDatasource.currentOutlet(); final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser(); final user = await _authLocalDataProvider.currentUser();
return _printKitchen(order: order, outlet: outlet, cashieName: user.name); return _printKitchen(order: order, outlet: outlet, cashieName: user.name);
}); });
} }
} }
@@ -40,6 +40,7 @@ class ProductDto with _$ProductDto {
@JsonKey(name: "business_type") String? businessType, @JsonKey(name: "business_type") String? businessType,
@JsonKey(name: "image_url") String? imageUrl, @JsonKey(name: "image_url") String? imageUrl,
@JsonKey(name: "printer_type") String? printerType, @JsonKey(name: "printer_type") String? printerType,
@JsonKey(name: "print_to_checker") bool? printToChecker,
@JsonKey(name: "metadata") Map<String, dynamic>? metadata, @JsonKey(name: "metadata") Map<String, dynamic>? metadata,
@JsonKey(name: "is_active") bool? isActive, @JsonKey(name: "is_active") bool? isActive,
@JsonKey(name: "created_at") String? createdAt, @JsonKey(name: "created_at") String? createdAt,
@@ -63,6 +64,7 @@ class ProductDto with _$ProductDto {
businessType: businessType ?? '', businessType: businessType ?? '',
imageUrl: imageUrl ?? '', imageUrl: imageUrl ?? '',
printerType: printerType ?? '', printerType: printerType ?? '',
printToChecker: printToChecker ?? false,
metadata: metadata ?? {}, metadata: metadata ?? {},
isActive: isActive ?? false, isActive: isActive ?? false,
createdAt: createdAt ?? '', createdAt: createdAt ?? '',
@@ -82,6 +84,7 @@ class ProductDto with _$ProductDto {
'business_type': businessType, 'business_type': businessType,
'image_url': imageUrl, 'image_url': imageUrl,
'printer_type': printerType, 'printer_type': printerType,
'print_to_checker': printToChecker == true ? 1 : 0,
'metadata': metadata != null ? jsonEncode(metadata) : null, 'metadata': metadata != null ? jsonEncode(metadata) : null,
'is_active': isActive == true ? 1 : 0, 'is_active': isActive == true ? 1 : 0,
'created_at': createdAt, 'created_at': createdAt,
@@ -103,6 +106,9 @@ class ProductDto with _$ProductDto {
businessType: map['business_type'] as String?, businessType: map['business_type'] as String?,
imageUrl: map['image_url'] as String?, imageUrl: map['image_url'] as String?,
printerType: map['printer_type'] as String?, printerType: map['printer_type'] as String?,
printToChecker: map['print_to_checker'] != null
? (map['print_to_checker'] as int) == 1
: null,
metadata: map['metadata'] != null metadata: map['metadata'] != null
? jsonDecode(map['metadata'] as String) as Map<String, dynamic> ? jsonDecode(map['metadata'] as String) as Map<String, dynamic>
: null, : null,
@@ -125,6 +131,7 @@ class ProductDto with _$ProductDto {
businessType: product.businessType, businessType: product.businessType,
imageUrl: product.imageUrl, imageUrl: product.imageUrl,
printerType: product.printerType, printerType: product.printerType,
printToChecker: product.printToChecker,
metadata: product.metadata, metadata: product.metadata,
isActive: product.isActive, isActive: product.isActive,
createdAt: product.createdAt, createdAt: product.createdAt,
@@ -321,6 +321,8 @@ mixin _$ProductDto {
String? get imageUrl => throw _privateConstructorUsedError; String? get imageUrl => throw _privateConstructorUsedError;
@JsonKey(name: "printer_type") @JsonKey(name: "printer_type")
String? get printerType => throw _privateConstructorUsedError; String? get printerType => throw _privateConstructorUsedError;
@JsonKey(name: "print_to_checker")
bool? get printToChecker => throw _privateConstructorUsedError;
@JsonKey(name: "metadata") @JsonKey(name: "metadata")
Map<String, dynamic>? get metadata => throw _privateConstructorUsedError; Map<String, dynamic>? get metadata => throw _privateConstructorUsedError;
@JsonKey(name: "is_active") @JsonKey(name: "is_active")
@@ -361,6 +363,7 @@ abstract class $ProductDtoCopyWith<$Res> {
@JsonKey(name: "business_type") String? businessType, @JsonKey(name: "business_type") String? businessType,
@JsonKey(name: "image_url") String? imageUrl, @JsonKey(name: "image_url") String? imageUrl,
@JsonKey(name: "printer_type") String? printerType, @JsonKey(name: "printer_type") String? printerType,
@JsonKey(name: "print_to_checker") bool? printToChecker,
@JsonKey(name: "metadata") Map<String, dynamic>? metadata, @JsonKey(name: "metadata") Map<String, dynamic>? metadata,
@JsonKey(name: "is_active") bool? isActive, @JsonKey(name: "is_active") bool? isActive,
@JsonKey(name: "created_at") String? createdAt, @JsonKey(name: "created_at") String? createdAt,
@@ -395,6 +398,7 @@ class _$ProductDtoCopyWithImpl<$Res, $Val extends ProductDto>
Object? businessType = freezed, Object? businessType = freezed,
Object? imageUrl = freezed, Object? imageUrl = freezed,
Object? printerType = freezed, Object? printerType = freezed,
Object? printToChecker = freezed,
Object? metadata = freezed, Object? metadata = freezed,
Object? isActive = freezed, Object? isActive = freezed,
Object? createdAt = freezed, Object? createdAt = freezed,
@@ -447,6 +451,10 @@ class _$ProductDtoCopyWithImpl<$Res, $Val extends ProductDto>
? _value.printerType ? _value.printerType
: printerType // ignore: cast_nullable_to_non_nullable : printerType // ignore: cast_nullable_to_non_nullable
as String?, as String?,
printToChecker: freezed == printToChecker
? _value.printToChecker
: printToChecker // ignore: cast_nullable_to_non_nullable
as bool?,
metadata: freezed == metadata metadata: freezed == metadata
? _value.metadata ? _value.metadata
: metadata // ignore: cast_nullable_to_non_nullable : metadata // ignore: cast_nullable_to_non_nullable
@@ -494,6 +502,7 @@ abstract class _$$ProductDtoImplCopyWith<$Res>
@JsonKey(name: "business_type") String? businessType, @JsonKey(name: "business_type") String? businessType,
@JsonKey(name: "image_url") String? imageUrl, @JsonKey(name: "image_url") String? imageUrl,
@JsonKey(name: "printer_type") String? printerType, @JsonKey(name: "printer_type") String? printerType,
@JsonKey(name: "print_to_checker") bool? printToChecker,
@JsonKey(name: "metadata") Map<String, dynamic>? metadata, @JsonKey(name: "metadata") Map<String, dynamic>? metadata,
@JsonKey(name: "is_active") bool? isActive, @JsonKey(name: "is_active") bool? isActive,
@JsonKey(name: "created_at") String? createdAt, @JsonKey(name: "created_at") String? createdAt,
@@ -527,6 +536,7 @@ class __$$ProductDtoImplCopyWithImpl<$Res>
Object? businessType = freezed, Object? businessType = freezed,
Object? imageUrl = freezed, Object? imageUrl = freezed,
Object? printerType = freezed, Object? printerType = freezed,
Object? printToChecker = freezed,
Object? metadata = freezed, Object? metadata = freezed,
Object? isActive = freezed, Object? isActive = freezed,
Object? createdAt = freezed, Object? createdAt = freezed,
@@ -579,6 +589,10 @@ class __$$ProductDtoImplCopyWithImpl<$Res>
? _value.printerType ? _value.printerType
: printerType // ignore: cast_nullable_to_non_nullable : printerType // ignore: cast_nullable_to_non_nullable
as String?, as String?,
printToChecker: freezed == printToChecker
? _value.printToChecker
: printToChecker // ignore: cast_nullable_to_non_nullable
as bool?,
metadata: freezed == metadata metadata: freezed == metadata
? _value._metadata ? _value._metadata
: metadata // ignore: cast_nullable_to_non_nullable : metadata // ignore: cast_nullable_to_non_nullable
@@ -619,6 +633,7 @@ class _$ProductDtoImpl extends _ProductDto {
@JsonKey(name: "business_type") this.businessType, @JsonKey(name: "business_type") this.businessType,
@JsonKey(name: "image_url") this.imageUrl, @JsonKey(name: "image_url") this.imageUrl,
@JsonKey(name: "printer_type") this.printerType, @JsonKey(name: "printer_type") this.printerType,
@JsonKey(name: "print_to_checker") this.printToChecker,
@JsonKey(name: "metadata") final Map<String, dynamic>? metadata, @JsonKey(name: "metadata") final Map<String, dynamic>? metadata,
@JsonKey(name: "is_active") this.isActive, @JsonKey(name: "is_active") this.isActive,
@JsonKey(name: "created_at") this.createdAt, @JsonKey(name: "created_at") this.createdAt,
@@ -664,6 +679,9 @@ class _$ProductDtoImpl extends _ProductDto {
@override @override
@JsonKey(name: "printer_type") @JsonKey(name: "printer_type")
final String? printerType; final String? printerType;
@override
@JsonKey(name: "print_to_checker")
final bool? printToChecker;
final Map<String, dynamic>? _metadata; final Map<String, dynamic>? _metadata;
@override @override
@JsonKey(name: "metadata") @JsonKey(name: "metadata")
@@ -697,7 +715,7 @@ class _$ProductDtoImpl extends _ProductDto {
@override @override
String toString() { String toString() {
return 'ProductDto(id: $id, organizationId: $organizationId, categoryId: $categoryId, sku: $sku, name: $name, description: $description, price: $price, cost: $cost, businessType: $businessType, imageUrl: $imageUrl, printerType: $printerType, metadata: $metadata, isActive: $isActive, createdAt: $createdAt, updatedAt: $updatedAt, variants: $variants)'; return 'ProductDto(id: $id, organizationId: $organizationId, categoryId: $categoryId, sku: $sku, name: $name, description: $description, price: $price, cost: $cost, businessType: $businessType, imageUrl: $imageUrl, printerType: $printerType, printToChecker: $printToChecker, metadata: $metadata, isActive: $isActive, createdAt: $createdAt, updatedAt: $updatedAt, variants: $variants)';
} }
@override @override
@@ -722,6 +740,8 @@ class _$ProductDtoImpl extends _ProductDto {
other.imageUrl == imageUrl) && other.imageUrl == imageUrl) &&
(identical(other.printerType, printerType) || (identical(other.printerType, printerType) ||
other.printerType == printerType) && other.printerType == printerType) &&
(identical(other.printToChecker, printToChecker) ||
other.printToChecker == printToChecker) &&
const DeepCollectionEquality().equals(other._metadata, _metadata) && const DeepCollectionEquality().equals(other._metadata, _metadata) &&
(identical(other.isActive, isActive) || (identical(other.isActive, isActive) ||
other.isActive == isActive) && other.isActive == isActive) &&
@@ -747,6 +767,7 @@ class _$ProductDtoImpl extends _ProductDto {
businessType, businessType,
imageUrl, imageUrl,
printerType, printerType,
printToChecker,
const DeepCollectionEquality().hash(_metadata), const DeepCollectionEquality().hash(_metadata),
isActive, isActive,
createdAt, createdAt,
@@ -781,6 +802,7 @@ abstract class _ProductDto extends ProductDto {
@JsonKey(name: "business_type") final String? businessType, @JsonKey(name: "business_type") final String? businessType,
@JsonKey(name: "image_url") final String? imageUrl, @JsonKey(name: "image_url") final String? imageUrl,
@JsonKey(name: "printer_type") final String? printerType, @JsonKey(name: "printer_type") final String? printerType,
@JsonKey(name: "print_to_checker") final bool? printToChecker,
@JsonKey(name: "metadata") final Map<String, dynamic>? metadata, @JsonKey(name: "metadata") final Map<String, dynamic>? metadata,
@JsonKey(name: "is_active") final bool? isActive, @JsonKey(name: "is_active") final bool? isActive,
@JsonKey(name: "created_at") final String? createdAt, @JsonKey(name: "created_at") final String? createdAt,
@@ -826,6 +848,9 @@ abstract class _ProductDto extends ProductDto {
@JsonKey(name: "printer_type") @JsonKey(name: "printer_type")
String? get printerType; String? get printerType;
@override @override
@JsonKey(name: "print_to_checker")
bool? get printToChecker;
@override
@JsonKey(name: "metadata") @JsonKey(name: "metadata")
Map<String, dynamic>? get metadata; Map<String, dynamic>? get metadata;
@override @override
@@ -40,6 +40,7 @@ _$ProductDtoImpl _$$ProductDtoImplFromJson(Map<String, dynamic> json) =>
businessType: json['business_type'] as String?, businessType: json['business_type'] as String?,
imageUrl: json['image_url'] as String?, imageUrl: json['image_url'] as String?,
printerType: json['printer_type'] as String?, printerType: json['printer_type'] as String?,
printToChecker: json['print_to_checker'] as bool?,
metadata: json['metadata'] as Map<String, dynamic>?, metadata: json['metadata'] as Map<String, dynamic>?,
isActive: json['is_active'] as bool?, isActive: json['is_active'] as bool?,
createdAt: json['created_at'] as String?, createdAt: json['created_at'] as String?,
@@ -62,6 +63,7 @@ Map<String, dynamic> _$$ProductDtoImplToJson(_$ProductDtoImpl instance) =>
'business_type': instance.businessType, 'business_type': instance.businessType,
'image_url': instance.imageUrl, 'image_url': instance.imageUrl,
'printer_type': instance.printerType, 'printer_type': instance.printerType,
'print_to_checker': instance.printToChecker,
'metadata': instance.metadata, 'metadata': instance.metadata,
'is_active': instance.isActive, 'is_active': instance.isActive,
'created_at': instance.createdAt, 'created_at': instance.createdAt,
+2 -3
View File
@@ -44,9 +44,8 @@ void main() async {
debugPrint = (message, {wrapWidth}) => ''; debugPrint = (message, {wrapWidth}) => '';
} }
await configureDependencies( const String env = String.fromEnvironment('ENV', defaultValue: 'dev');
kReleaseMode ? Environment.prod : Environment.dev, await configureDependencies(env);
);
// Inisialisasi FCM setelah DI siap // Inisialisasi FCM setelah DI siap
await getIt<FcmService>().initialize(); await getIt<FcmService>().initialize();
@@ -28,18 +28,20 @@ class OrderVoidConfirmDialog extends StatelessWidget {
children: [ children: [
Expanded( Expanded(
child: AppElevatedButton.outlined( child: AppElevatedButton.outlined(
onPressed: () => context.maybePop(), onPressed: state.isSubmitting ? null : () => context.maybePop(),
label: 'Batal', label: 'Batal',
), ),
), ),
const SpaceWidth(16), const SpaceWidth(16),
Expanded( Expanded(
child: AppElevatedButton.filled( child: AppElevatedButton.filled(
onPressed: () { onPressed: state.isSubmitting
context.read<VoidFormBloc>().add( ? null
const VoidFormEvent.submitted(), : () {
); context.read<VoidFormBloc>().add(
}, const VoidFormEvent.submitted(),
);
},
label: state.voidType.isAll ? 'Void Pesanan' : 'Void Item', label: state.voidType.isAll ? 'Void Pesanan' : 'Void Item',
isLoading: state.isSubmitting, isLoading: state.isSubmitting,
), ),
@@ -117,7 +117,7 @@ class PrintUi {
bytes += builder.separator(); bytes += builder.separator();
// Items — qty NAMA (uppercase), variant indent // Items — qty NAMA (uppercase), variant indent
for (final item in order.orderItems) { for (final item in order.orderItems.where((e) => e.printToChecker)) {
final name = item.productName.toUpperCase(); final name = item.productName.toUpperCase();
bytes += builder.itemText('${item.quantity} $name'); bytes += builder.itemText('${item.quantity} $name');
if (item.productVariantName.isNotEmpty) { if (item.productVariantName.isNotEmpty) {
+6 -6
View File
@@ -852,10 +852,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.0" version: "1.18.0"
mime: mime:
dependency: transitive dependency: transitive
description: description:
@@ -1337,10 +1337,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.10" version: "0.7.11"
time: time:
dependency: transitive dependency: transitive
description: description:
@@ -1374,7 +1374,7 @@ packages:
source: hosted source: hosted
version: "1.4.0" version: "1.4.0"
uuid: uuid:
dependency: transitive dependency: "direct main"
description: description:
name: uuid name: uuid
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
@@ -1502,5 +1502,5 @@ packages:
source: hosted source: hosted
version: "3.1.3" version: "3.1.3"
sdks: sdks:
dart: ">=3.9.0-0 <4.0.0" dart: ">=3.10.0-0 <4.0.0"
flutter: ">=3.29.0" flutter: ">=3.29.0"
+2 -1
View File
@@ -3,7 +3,7 @@ description: "A new Flutter project."
publish_to: "none" publish_to: "none"
version: 1.0.7+13 version: 1.0.11+17
environment: environment:
sdk: ^3.8.1 sdk: ^3.8.1
@@ -49,6 +49,7 @@ dependencies:
table_calendar: ^3.1.2 table_calendar: ^3.1.2
synchronized: ^3.4.0 synchronized: ^3.4.0
collection: ^1.19.1 collection: ^1.19.1
uuid: ^4.5.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: