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
android.useAndroidX=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:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart' hide Order;
import 'package:uuid/uuid.dart';
import '../../../common/types/order_type.dart';
import '../../../domain/customer/customer.dart';
@@ -123,10 +124,15 @@ class OrderFormBloc extends Bloc<OrderFormEvent, OrderFormState> {
addItemOrder: (e) async {
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(
state.copyWith(
isAddingItemOrder: true,
failureOrAddItemOrder: none(),
addItemIdempotencyKey: idempotencyKey,
),
);
@@ -145,12 +151,16 @@ class OrderFormBloc extends Bloc<OrderFormEvent, OrderFormState> {
failureOrAddItemOrder = await _repository.addItemOrder(
id: e.orderId,
request: request,
idempotencyKey: idempotencyKey,
);
emit(
state.copyWith(
isAddingItemOrder: false,
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 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
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -1608,6 +1613,7 @@ abstract class $OrderFormStateCopyWith<$Res> {
bool isCreating,
bool isCreatingWithPayment,
bool isAddingItemOrder,
String? addItemIdempotencyKey,
});
$PaymentMethodCopyWith<$Res>? get paymentMethod;
@@ -1638,6 +1644,7 @@ class _$OrderFormStateCopyWithImpl<$Res, $Val extends OrderFormState>
Object? isCreating = null,
Object? isCreatingWithPayment = null,
Object? isAddingItemOrder = null,
Object? addItemIdempotencyKey = freezed,
}) {
return _then(
_value.copyWith(
@@ -1678,6 +1685,10 @@ class _$OrderFormStateCopyWithImpl<$Res, $Val extends OrderFormState>
? _value.isAddingItemOrder
: isAddingItemOrder // ignore: cast_nullable_to_non_nullable
as bool,
addItemIdempotencyKey: freezed == addItemIdempotencyKey
? _value.addItemIdempotencyKey
: addItemIdempotencyKey // ignore: cast_nullable_to_non_nullable
as String?,
)
as $Val,
);
@@ -1731,6 +1742,7 @@ abstract class _$$OrderFormStateImplCopyWith<$Res>
bool isCreating,
bool isCreatingWithPayment,
bool isAddingItemOrder,
String? addItemIdempotencyKey,
});
@override
@@ -1762,6 +1774,7 @@ class __$$OrderFormStateImplCopyWithImpl<$Res>
Object? isCreating = null,
Object? isCreatingWithPayment = null,
Object? isAddingItemOrder = null,
Object? addItemIdempotencyKey = freezed,
}) {
return _then(
_$OrderFormStateImpl(
@@ -1801,6 +1814,10 @@ class __$$OrderFormStateImplCopyWithImpl<$Res>
? _value.isAddingItemOrder
: isAddingItemOrder // ignore: cast_nullable_to_non_nullable
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.isCreatingWithPayment = false,
this.isAddingItemOrder = false,
this.addItemIdempotencyKey,
});
@override
@@ -1845,9 +1863,15 @@ class _$OrderFormStateImpl
@JsonKey()
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
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
@@ -1868,7 +1892,10 @@ class _$OrderFormStateImpl
..add(DiagnosticsProperty('failureOrAddItemOrder', failureOrAddItemOrder))
..add(DiagnosticsProperty('isCreating', isCreating))
..add(DiagnosticsProperty('isCreatingWithPayment', isCreatingWithPayment))
..add(DiagnosticsProperty('isAddingItemOrder', isAddingItemOrder));
..add(DiagnosticsProperty('isAddingItemOrder', isAddingItemOrder))
..add(
DiagnosticsProperty('addItemIdempotencyKey', addItemIdempotencyKey),
);
}
@override
@@ -1897,7 +1924,9 @@ class _$OrderFormStateImpl
(identical(other.isCreatingWithPayment, isCreatingWithPayment) ||
other.isCreatingWithPayment == isCreatingWithPayment) &&
(identical(other.isAddingItemOrder, isAddingItemOrder) ||
other.isAddingItemOrder == isAddingItemOrder));
other.isAddingItemOrder == isAddingItemOrder) &&
(identical(other.addItemIdempotencyKey, addItemIdempotencyKey) ||
other.addItemIdempotencyKey == addItemIdempotencyKey));
}
@override
@@ -1912,6 +1941,7 @@ class _$OrderFormStateImpl
isCreating,
isCreatingWithPayment,
isAddingItemOrder,
addItemIdempotencyKey,
);
/// Create a copy of OrderFormState
@@ -1938,6 +1968,7 @@ abstract class _OrderFormState implements OrderFormState {
final bool isCreating,
final bool isCreatingWithPayment,
final bool isAddingItemOrder,
final String? addItemIdempotencyKey,
}) = _$OrderFormStateImpl;
@override
@@ -1959,6 +1990,12 @@ abstract class _OrderFormState implements OrderFormState {
@override
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
/// with the given fields replaced by the non-null parameter values.
@override
@@ -13,6 +13,10 @@ class OrderFormState with _$OrderFormState {
@Default(false) bool isCreating,
@Default(false) bool isCreatingWithPayment,
@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;
factory OrderFormState.initial() => OrderFormState(
@@ -4,6 +4,7 @@ import 'package:bloc/bloc.dart';
import 'package:dartz/dartz.dart' hide Order;
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart' hide Order;
import 'package:uuid/uuid.dart';
import '../../../common/types/split_type.dart';
import '../../../domain/order/order.dart';
@@ -38,7 +39,15 @@ class PaymentFormBloc extends Bloc<PaymentFormEvent, PaymentFormState> {
submitted: (e) async {
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(
orderId: state.order.id,
@@ -58,20 +67,32 @@ class PaymentFormBloc extends Bloc<PaymentFormEvent, PaymentFormState> {
.toList(),
);
failureOrPayment = await _repository.createPayment(request: request);
failureOrPayment = await _repository.createPayment(
request: request,
idempotencyKey: idempotencyKey,
);
emit(
state.copyWith(
isSubmitting: false,
failureOrPayment: optionOf(failureOrPayment),
// Clear key on success, keep on failure for retry
idempotencyKey: failureOrPayment.isRight() ? null : idempotencyKey,
),
);
},
submittedSplitBill: (e) async {
Either<OrderFailure, Payment> failureOrPayment;
// Generate new idempotency key untuk split bill
final idempotencyKey = state.splitBillIdempotencyKey ?? const Uuid().v4();
emit(
state.copyWith(isSubmitting: true, failureOrPaymentSplitBill: none()),
state.copyWith(
isSubmitting: true,
failureOrPaymentSplitBill: none(),
splitBillIdempotencyKey: idempotencyKey,
),
);
final request = PaymentSplitBillRequest(
@@ -93,12 +114,18 @@ class PaymentFormBloc extends Bloc<PaymentFormEvent, PaymentFormState> {
log(request.toString());
failureOrPayment = await _repository.createSplitBill(request);
failureOrPayment = await _repository.createSplitBill(
request,
idempotencyKey: idempotencyKey,
);
emit(
state.copyWith(
isSubmitting: false,
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;
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
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -834,6 +842,8 @@ abstract class $PaymentFormStateCopyWith<$Res> {
Option<Either<OrderFailure, Payment>> failureOrPaymentSplitBill,
PaymentMethod? paymentMethod,
bool isSubmitting,
String? idempotencyKey,
String? splitBillIdempotencyKey,
});
$OrderCopyWith<$Res> get order;
@@ -861,6 +871,8 @@ class _$PaymentFormStateCopyWithImpl<$Res, $Val extends PaymentFormState>
Object? failureOrPaymentSplitBill = null,
Object? paymentMethod = freezed,
Object? isSubmitting = null,
Object? idempotencyKey = freezed,
Object? splitBillIdempotencyKey = freezed,
}) {
return _then(
_value.copyWith(
@@ -888,6 +900,14 @@ class _$PaymentFormStateCopyWithImpl<$Res, $Val extends PaymentFormState>
? _value.isSubmitting
: isSubmitting // ignore: cast_nullable_to_non_nullable
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,
);
@@ -934,6 +954,8 @@ abstract class _$$PaymentFormStateImplCopyWith<$Res>
Option<Either<OrderFailure, Payment>> failureOrPaymentSplitBill,
PaymentMethod? paymentMethod,
bool isSubmitting,
String? idempotencyKey,
String? splitBillIdempotencyKey,
});
@override
@@ -962,6 +984,8 @@ class __$$PaymentFormStateImplCopyWithImpl<$Res>
Object? failureOrPaymentSplitBill = null,
Object? paymentMethod = freezed,
Object? isSubmitting = null,
Object? idempotencyKey = freezed,
Object? splitBillIdempotencyKey = freezed,
}) {
return _then(
_$PaymentFormStateImpl(
@@ -989,6 +1013,14 @@ class __$$PaymentFormStateImplCopyWithImpl<$Res>
? _value.isSubmitting
: isSubmitting // ignore: cast_nullable_to_non_nullable
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,
this.paymentMethod,
this.isSubmitting = false,
this.idempotencyKey,
this.splitBillIdempotencyKey,
}) : _pendingItems = pendingItems;
@override
@@ -1026,9 +1060,19 @@ class _$PaymentFormStateImpl implements _PaymentFormState {
@JsonKey()
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
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
@@ -1051,7 +1095,14 @@ class _$PaymentFormStateImpl implements _PaymentFormState {
(identical(other.paymentMethod, paymentMethod) ||
other.paymentMethod == paymentMethod) &&
(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
@@ -1063,6 +1114,8 @@ class _$PaymentFormStateImpl implements _PaymentFormState {
failureOrPaymentSplitBill,
paymentMethod,
isSubmitting,
idempotencyKey,
splitBillIdempotencyKey,
);
/// Create a copy of PaymentFormState
@@ -1086,6 +1139,8 @@ abstract class _PaymentFormState implements PaymentFormState {
failureOrPaymentSplitBill,
final PaymentMethod? paymentMethod,
final bool isSubmitting,
final String? idempotencyKey,
final String? splitBillIdempotencyKey,
}) = _$PaymentFormStateImpl;
@override
@@ -1101,6 +1156,16 @@ abstract class _PaymentFormState implements PaymentFormState {
@override
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
/// with the given fields replaced by the non-null parameter values.
@override
@@ -9,6 +9,12 @@ class PaymentFormState with _$PaymentFormState {
required Option<Either<OrderFailure, Payment>> failureOrPaymentSplitBill,
PaymentMethod? paymentMethod,
@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;
factory PaymentFormState.initial() => PaymentFormState(
@@ -2,6 +2,7 @@ import 'package:bloc/bloc.dart';
import 'package:dartz/dartz.dart' hide Order;
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart' hide Order;
import 'package:uuid/uuid.dart';
import '../../../common/data/refund_data.dart';
import '../../../domain/order/order.dart';
@@ -34,7 +35,15 @@ class RefundFormBloc extends Bloc<RefundFormEvent, RefundFormState> {
submitted: (e) async {
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(
id: state.order.id,
@@ -42,12 +51,15 @@ class RefundFormBloc extends Bloc<RefundFormEvent, RefundFormState> {
? state.reason
: state.refundReason?.value ?? '',
refundAmount: state.order.totalAmount,
idempotencyKey: idempotencyKey,
);
emit(
state.copyWith(
isSubmitting: false,
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;
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
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -712,6 +717,7 @@ abstract class $RefundFormStateCopyWith<$Res> {
RefundReason? refundReason,
Option<Either<OrderFailure, Unit>> failureOrRefund,
bool isSubmitting,
String? idempotencyKey,
});
$OrderCopyWith<$Res> get order;
@@ -737,6 +743,7 @@ class _$RefundFormStateCopyWithImpl<$Res, $Val extends RefundFormState>
Object? refundReason = freezed,
Object? failureOrRefund = null,
Object? isSubmitting = null,
Object? idempotencyKey = freezed,
}) {
return _then(
_value.copyWith(
@@ -760,6 +767,10 @@ class _$RefundFormStateCopyWithImpl<$Res, $Val extends RefundFormState>
? _value.isSubmitting
: isSubmitting // ignore: cast_nullable_to_non_nullable
as bool,
idempotencyKey: freezed == idempotencyKey
? _value.idempotencyKey
: idempotencyKey // ignore: cast_nullable_to_non_nullable
as String?,
)
as $Val,
);
@@ -791,6 +802,7 @@ abstract class _$$RefundFormStateImplCopyWith<$Res>
RefundReason? refundReason,
Option<Either<OrderFailure, Unit>> failureOrRefund,
bool isSubmitting,
String? idempotencyKey,
});
@override
@@ -816,6 +828,7 @@ class __$$RefundFormStateImplCopyWithImpl<$Res>
Object? refundReason = freezed,
Object? failureOrRefund = null,
Object? isSubmitting = null,
Object? idempotencyKey = freezed,
}) {
return _then(
_$RefundFormStateImpl(
@@ -839,6 +852,10 @@ class __$$RefundFormStateImplCopyWithImpl<$Res>
? _value.isSubmitting
: isSubmitting // ignore: cast_nullable_to_non_nullable
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,
required this.failureOrRefund,
this.isSubmitting = false,
this.idempotencyKey,
});
@override
@@ -867,9 +885,15 @@ class _$RefundFormStateImpl implements _RefundFormState {
@JsonKey()
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
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
@@ -884,7 +908,9 @@ class _$RefundFormStateImpl implements _RefundFormState {
(identical(other.failureOrRefund, failureOrRefund) ||
other.failureOrRefund == failureOrRefund) &&
(identical(other.isSubmitting, isSubmitting) ||
other.isSubmitting == isSubmitting));
other.isSubmitting == isSubmitting) &&
(identical(other.idempotencyKey, idempotencyKey) ||
other.idempotencyKey == idempotencyKey));
}
@override
@@ -895,6 +921,7 @@ class _$RefundFormStateImpl implements _RefundFormState {
refundReason,
failureOrRefund,
isSubmitting,
idempotencyKey,
);
/// Create a copy of RefundFormState
@@ -916,6 +943,7 @@ abstract class _RefundFormState implements RefundFormState {
final RefundReason? refundReason,
required final Option<Either<OrderFailure, Unit>> failureOrRefund,
final bool isSubmitting,
final String? idempotencyKey,
}) = _$RefundFormStateImpl;
@override
@@ -929,6 +957,12 @@ abstract class _RefundFormState implements RefundFormState {
@override
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
/// with the given fields replaced by the non-null parameter values.
@override
@@ -8,6 +8,10 @@ class RefundFormState with _$RefundFormState {
RefundReason? refundReason,
required Option<Either<OrderFailure, Unit>> failureOrRefund,
@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;
factory RefundFormState.initial() => RefundFormState(
@@ -3,6 +3,7 @@ import 'package:collection/collection.dart';
import 'package:dartz/dartz.dart' hide Order;
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart' hide Order;
import 'package:uuid/uuid.dart';
import '../../../common/types/void_type.dart';
import '../../../domain/order/order.dart';
@@ -99,18 +100,28 @@ class VoidFormBloc extends Bloc<VoidFormEvent, VoidFormState> {
updatedAt: originalItem.updatedAt,
printerType: originalItem.printerType,
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(
orderId: state.order.id,
reason: state.voidReason ?? '',
orderItems: voidItems,
type: state.voidType.toStringType(),
idempotencyKey: idempotencyKey,
);
emit(
@@ -118,6 +129,8 @@ class VoidFormBloc extends Bloc<VoidFormEvent, VoidFormState> {
isSubmitting: false,
failureOrVoid: optionOf(failureOrVoid),
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;
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
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -1280,6 +1285,7 @@ abstract class $VoidFormStateCopyWith<$Res> {
int totalPriceVoid,
Option<Either<OrderFailure, Unit>> failureOrVoid,
bool isSubmitting,
String? idempotencyKey,
});
$OrderCopyWith<$Res> get order;
@@ -1309,6 +1315,7 @@ class _$VoidFormStateCopyWithImpl<$Res, $Val extends VoidFormState>
Object? totalPriceVoid = null,
Object? failureOrVoid = null,
Object? isSubmitting = null,
Object? idempotencyKey = freezed,
}) {
return _then(
_value.copyWith(
@@ -1348,6 +1355,10 @@ class _$VoidFormStateCopyWithImpl<$Res, $Val extends VoidFormState>
? _value.isSubmitting
: isSubmitting // ignore: cast_nullable_to_non_nullable
as bool,
idempotencyKey: freezed == idempotencyKey
? _value.idempotencyKey
: idempotencyKey // ignore: cast_nullable_to_non_nullable
as String?,
)
as $Val,
);
@@ -1383,6 +1394,7 @@ abstract class _$$VoidFormStateImplCopyWith<$Res>
int totalPriceVoid,
Option<Either<OrderFailure, Unit>> failureOrVoid,
bool isSubmitting,
String? idempotencyKey,
});
@override
@@ -1412,6 +1424,7 @@ class __$$VoidFormStateImplCopyWithImpl<$Res>
Object? totalPriceVoid = null,
Object? failureOrVoid = null,
Object? isSubmitting = null,
Object? idempotencyKey = freezed,
}) {
return _then(
_$VoidFormStateImpl(
@@ -1451,6 +1464,10 @@ class __$$VoidFormStateImplCopyWithImpl<$Res>
? _value.isSubmitting
: isSubmitting // ignore: cast_nullable_to_non_nullable
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.failureOrVoid,
this.isSubmitting = false,
this.idempotencyKey,
}) : _pendingItems = pendingItems,
_voidItems = voidItems,
_selectedItemQuantities = selectedItemQuantities;
@@ -1512,9 +1530,15 @@ class _$VoidFormStateImpl implements _VoidFormState {
@JsonKey()
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
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
@@ -1544,7 +1568,9 @@ class _$VoidFormStateImpl implements _VoidFormState {
(identical(other.failureOrVoid, failureOrVoid) ||
other.failureOrVoid == failureOrVoid) &&
(identical(other.isSubmitting, isSubmitting) ||
other.isSubmitting == isSubmitting));
other.isSubmitting == isSubmitting) &&
(identical(other.idempotencyKey, idempotencyKey) ||
other.idempotencyKey == idempotencyKey));
}
@override
@@ -1559,6 +1585,7 @@ class _$VoidFormStateImpl implements _VoidFormState {
totalPriceVoid,
failureOrVoid,
isSubmitting,
idempotencyKey,
);
/// Create a copy of VoidFormState
@@ -1581,6 +1608,7 @@ abstract class _VoidFormState implements VoidFormState {
required final int totalPriceVoid,
required final Option<Either<OrderFailure, Unit>> failureOrVoid,
final bool isSubmitting,
final String? idempotencyKey,
}) = _$VoidFormStateImpl;
@override
@@ -1602,6 +1630,12 @@ abstract class _VoidFormState implements VoidFormState {
@override
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
/// with the given fields replaced by the non-null parameter values.
@override
@@ -12,6 +12,10 @@ class VoidFormState with _$VoidFormState {
required int totalPriceVoid,
required Option<Either<OrderFailure, Unit>> failureOrVoid,
@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;
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/connection_timeout_interceptor.dart';
import 'interceptors/crashlytic_interceptor.dart';
import 'interceptors/idempotency_interceptor.dart';
import 'interceptors/internal_server_interceptor.dart';
import 'interceptors/not_found_interceptor.dart';
import 'interceptors/unauthorized_interceptor.dart';
@@ -30,6 +31,7 @@ class ApiClient {
_dio.options.connectTimeout = const Duration(seconds: 20);
_dio.options.validateStatus = (status) =>
status != null && status >= 200 && status < 500;
_dio.interceptors.add(IdempotencyInterceptor(_dio));
_dio.interceptors.add(BadNetworkErrorInterceptor());
_dio.interceptors.add(BadRequestErrorInterceptor());
_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(
path,
version: 1, // Updated version for categories table
version: 2,
onCreate: _onCreate,
onUpgrade: _onUpgrade,
);
@@ -38,6 +38,7 @@ class DatabaseHelper {
business_type TEXT,
image_url TEXT,
printer_type TEXT,
print_to_checker INTEGER DEFAULT 0,
metadata TEXT,
is_active INTEGER,
created_at TEXT,
@@ -107,7 +108,13 @@ class DatabaseHelper {
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 {
final db = await database;
@@ -125,6 +125,7 @@ class Order with _$Order {
createdAt: DateTime.now().subtract(const Duration(hours: 1)),
updatedAt: DateTime.now(),
printerType: 'Barista',
printToChecker: false,
paidQuantity: 2,
categoryId: 'CAT-001',
categoryName: 'Minuman',
@@ -145,6 +146,7 @@ class Order with _$Order {
createdAt: DateTime.now().subtract(const Duration(hours: 1)),
updatedAt: DateTime.now(),
printerType: 'Kitchen',
printToChecker: false,
paidQuantity: 1,
categoryId: 'CAT-002',
categoryName: 'Makanan',
@@ -165,6 +167,7 @@ class Order with _$Order {
createdAt: DateTime.now().subtract(const Duration(hours: 1)),
updatedAt: DateTime.now(),
printerType: 'Kitchen',
printToChecker: false,
paidQuantity: 1,
categoryId: 'CAT-002',
categoryName: 'Makanan',
@@ -230,6 +233,7 @@ class OrderItem with _$OrderItem {
required DateTime createdAt,
required DateTime updatedAt,
required String printerType,
required bool printToChecker,
required int paidQuantity,
required String categoryId,
required String categoryName,
@@ -251,6 +255,7 @@ class OrderItem with _$OrderItem {
createdAt: DateTime(1970),
updatedAt: DateTime(1970),
printerType: '',
printToChecker: false,
paidQuantity: 0,
categoryId: '',
categoryName: '',
@@ -274,6 +279,7 @@ class OrderItem with _$OrderItem {
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
printerType: productQuantity.product.printerType,
printToChecker: productQuantity.product.printToChecker,
paidQuantity: 0,
categoryId: '',
categoryName: '',
+25 -3
View File
@@ -1002,6 +1002,7 @@ mixin _$OrderItem {
DateTime get createdAt => throw _privateConstructorUsedError;
DateTime get updatedAt => throw _privateConstructorUsedError;
String get printerType => throw _privateConstructorUsedError;
bool get printToChecker => throw _privateConstructorUsedError;
int get paidQuantity => throw _privateConstructorUsedError;
String get categoryId => throw _privateConstructorUsedError;
String get categoryName => throw _privateConstructorUsedError;
@@ -1034,6 +1035,7 @@ abstract class $OrderItemCopyWith<$Res> {
DateTime createdAt,
DateTime updatedAt,
String printerType,
bool printToChecker,
int paidQuantity,
String categoryId,
String categoryName,
@@ -1070,6 +1072,7 @@ class _$OrderItemCopyWithImpl<$Res, $Val extends OrderItem>
Object? createdAt = null,
Object? updatedAt = null,
Object? printerType = null,
Object? printToChecker = null,
Object? paidQuantity = null,
Object? categoryId = null,
Object? categoryName = null,
@@ -1136,6 +1139,10 @@ class _$OrderItemCopyWithImpl<$Res, $Val extends OrderItem>
? _value.printerType
: printerType // ignore: cast_nullable_to_non_nullable
as String,
printToChecker: null == printToChecker
? _value.printToChecker
: printToChecker // ignore: cast_nullable_to_non_nullable
as bool,
paidQuantity: null == paidQuantity
? _value.paidQuantity
: paidQuantity // ignore: cast_nullable_to_non_nullable
@@ -1179,6 +1186,7 @@ abstract class _$$OrderItemImplCopyWith<$Res>
DateTime createdAt,
DateTime updatedAt,
String printerType,
bool printToChecker,
int paidQuantity,
String categoryId,
String categoryName,
@@ -1214,6 +1222,7 @@ class __$$OrderItemImplCopyWithImpl<$Res>
Object? createdAt = null,
Object? updatedAt = null,
Object? printerType = null,
Object? printToChecker = null,
Object? paidQuantity = null,
Object? categoryId = null,
Object? categoryName = null,
@@ -1280,6 +1289,10 @@ class __$$OrderItemImplCopyWithImpl<$Res>
? _value.printerType
: printerType // ignore: cast_nullable_to_non_nullable
as String,
printToChecker: null == printToChecker
? _value.printToChecker
: printToChecker // ignore: cast_nullable_to_non_nullable
as bool,
paidQuantity: null == paidQuantity
? _value.paidQuantity
: paidQuantity // ignore: cast_nullable_to_non_nullable
@@ -1316,6 +1329,7 @@ class _$OrderItemImpl implements _OrderItem {
required this.createdAt,
required this.updatedAt,
required this.printerType,
required this.printToChecker,
required this.paidQuantity,
required this.categoryId,
required this.categoryName,
@@ -1358,6 +1372,8 @@ class _$OrderItemImpl implements _OrderItem {
@override
final String printerType;
@override
final bool printToChecker;
@override
final int paidQuantity;
@override
final String categoryId;
@@ -1366,7 +1382,7 @@ class _$OrderItemImpl implements _OrderItem {
@override
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
@@ -1402,6 +1418,8 @@ class _$OrderItemImpl implements _OrderItem {
other.updatedAt == updatedAt) &&
(identical(other.printerType, printerType) ||
other.printerType == printerType) &&
(identical(other.printToChecker, printToChecker) ||
other.printToChecker == printToChecker) &&
(identical(other.paidQuantity, paidQuantity) ||
other.paidQuantity == paidQuantity) &&
(identical(other.categoryId, categoryId) ||
@@ -1411,7 +1429,7 @@ class _$OrderItemImpl implements _OrderItem {
}
@override
int get hashCode => Object.hash(
int get hashCode => Object.hashAll([
runtimeType,
id,
orderId,
@@ -1428,10 +1446,11 @@ class _$OrderItemImpl implements _OrderItem {
createdAt,
updatedAt,
printerType,
printToChecker,
paidQuantity,
categoryId,
categoryName,
);
]);
/// Create a copy of OrderItem
/// 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 updatedAt,
required final String printerType,
required final bool printToChecker,
required final int paidQuantity,
required final String categoryId,
required final String categoryName,
@@ -1495,6 +1515,8 @@ abstract class _OrderItem implements OrderItem {
@override
String get printerType;
@override
bool get printToChecker;
@override
int get paidQuantity;
@override
String get categoryId;
@@ -24,10 +24,12 @@ abstract class IOrderRepository {
Future<Either<OrderFailure, Order>> addItemOrder({
required String id,
required List<AddItemOrderRequest> request,
String? idempotencyKey,
});
Future<Either<OrderFailure, Payment>> createPayment({
required PaymentRequest request,
String? idempotencyKey,
});
Future<Either<OrderFailure, Unit>> voidOrder({
@@ -35,15 +37,18 @@ abstract class IOrderRepository {
required String reason,
String type = "ITEM", // TYPE: ALL, ITEM
required List<OrderItem> orderItems,
String? idempotencyKey,
});
Future<Either<OrderFailure, Payment>> createSplitBill(
PaymentSplitBillRequest request,
);
PaymentSplitBillRequest request, {
String? idempotencyKey,
});
Future<Either<OrderFailure, Unit>> refundOrder({
required String id,
required String reason,
required int refundAmount,
String? idempotencyKey,
});
}
@@ -33,6 +33,7 @@ class Product with _$Product {
required String businessType,
required String imageUrl,
required String printerType,
required bool printToChecker,
required Map<String, dynamic> metadata,
required bool isActive,
required String createdAt,
@@ -52,6 +53,7 @@ class Product with _$Product {
businessType: '',
imageUrl: '',
printerType: '',
printToChecker: false,
metadata: {},
isActive: false,
createdAt: '',
+24 -1
View File
@@ -279,6 +279,7 @@ mixin _$Product {
String get businessType => throw _privateConstructorUsedError;
String get imageUrl => throw _privateConstructorUsedError;
String get printerType => throw _privateConstructorUsedError;
bool get printToChecker => throw _privateConstructorUsedError;
Map<String, dynamic> get metadata => throw _privateConstructorUsedError;
bool get isActive => throw _privateConstructorUsedError;
String get createdAt => throw _privateConstructorUsedError;
@@ -308,6 +309,7 @@ abstract class $ProductCopyWith<$Res> {
String businessType,
String imageUrl,
String printerType,
bool printToChecker,
Map<String, dynamic> metadata,
bool isActive,
String createdAt,
@@ -342,6 +344,7 @@ class _$ProductCopyWithImpl<$Res, $Val extends Product>
Object? businessType = null,
Object? imageUrl = null,
Object? printerType = null,
Object? printToChecker = null,
Object? metadata = null,
Object? isActive = null,
Object? createdAt = null,
@@ -394,6 +397,10 @@ class _$ProductCopyWithImpl<$Res, $Val extends Product>
? _value.printerType
: printerType // ignore: cast_nullable_to_non_nullable
as String,
printToChecker: null == printToChecker
? _value.printToChecker
: printToChecker // ignore: cast_nullable_to_non_nullable
as bool,
metadata: null == metadata
? _value.metadata
: metadata // ignore: cast_nullable_to_non_nullable
@@ -440,6 +447,7 @@ abstract class _$$ProductImplCopyWith<$Res> implements $ProductCopyWith<$Res> {
String businessType,
String imageUrl,
String printerType,
bool printToChecker,
Map<String, dynamic> metadata,
bool isActive,
String createdAt,
@@ -473,6 +481,7 @@ class __$$ProductImplCopyWithImpl<$Res>
Object? businessType = null,
Object? imageUrl = null,
Object? printerType = null,
Object? printToChecker = null,
Object? metadata = null,
Object? isActive = null,
Object? createdAt = null,
@@ -525,6 +534,10 @@ class __$$ProductImplCopyWithImpl<$Res>
? _value.printerType
: printerType // ignore: cast_nullable_to_non_nullable
as String,
printToChecker: null == printToChecker
? _value.printToChecker
: printToChecker // ignore: cast_nullable_to_non_nullable
as bool,
metadata: null == metadata
? _value._metadata
: metadata // ignore: cast_nullable_to_non_nullable
@@ -565,6 +578,7 @@ class _$ProductImpl with DiagnosticableTreeMixin implements _Product {
required this.businessType,
required this.imageUrl,
required this.printerType,
required this.printToChecker,
required final Map<String, dynamic> metadata,
required this.isActive,
required this.createdAt,
@@ -595,6 +609,8 @@ class _$ProductImpl with DiagnosticableTreeMixin implements _Product {
final String imageUrl;
@override
final String printerType;
@override
final bool printToChecker;
final Map<String, dynamic> _metadata;
@override
Map<String, dynamic> get metadata {
@@ -619,7 +635,7 @@ class _$ProductImpl with DiagnosticableTreeMixin implements _Product {
@override
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
@@ -638,6 +654,7 @@ class _$ProductImpl with DiagnosticableTreeMixin implements _Product {
..add(DiagnosticsProperty('businessType', businessType))
..add(DiagnosticsProperty('imageUrl', imageUrl))
..add(DiagnosticsProperty('printerType', printerType))
..add(DiagnosticsProperty('printToChecker', printToChecker))
..add(DiagnosticsProperty('metadata', metadata))
..add(DiagnosticsProperty('isActive', isActive))
..add(DiagnosticsProperty('createdAt', createdAt))
@@ -667,6 +684,8 @@ class _$ProductImpl with DiagnosticableTreeMixin implements _Product {
other.imageUrl == imageUrl) &&
(identical(other.printerType, printerType) ||
other.printerType == printerType) &&
(identical(other.printToChecker, printToChecker) ||
other.printToChecker == printToChecker) &&
const DeepCollectionEquality().equals(other._metadata, _metadata) &&
(identical(other.isActive, isActive) ||
other.isActive == isActive) &&
@@ -691,6 +710,7 @@ class _$ProductImpl with DiagnosticableTreeMixin implements _Product {
businessType,
imageUrl,
printerType,
printToChecker,
const DeepCollectionEquality().hash(_metadata),
isActive,
createdAt,
@@ -720,6 +740,7 @@ abstract class _Product implements Product {
required final String businessType,
required final String imageUrl,
required final String printerType,
required final bool printToChecker,
required final Map<String, dynamic> metadata,
required final bool isActive,
required final String createdAt,
@@ -750,6 +771,8 @@ abstract class _Product implements Product {
@override
String get printerType;
@override
bool get printToChecker;
@override
Map<String, dynamic> get metadata;
@override
bool get isActive;
+4 -4
View File
@@ -10,18 +10,18 @@ abstract class Env {
@dev
class DevEnv implements Env {
@override
String get baseUrl => 'https://api-pos.apskel.id';
String get baseUrl => 'http://194.233.78.1:4001';
@override
String get dbName => "apskel_pos_dev.db"; // example value
String get dbName => "apskel_pos_staging.db";
}
@Injectable(as: Env)
@prod
class ProdEnv implements Env {
@override
String get baseUrl => 'https://api-pos.apskel.id';
String get baseUrl => 'https://enaklo-pos-api.altru.id';
@override
String get dbName => "apskel_pos_dev.db";
String get dbName => "apskel_pos_prod.db";
}
@@ -18,6 +18,15 @@ class OrderRemoteDataProvider {
final _logName = 'OrderRemoteDataProvider';
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({
int page = 1,
int limit = 10,
@@ -151,13 +160,14 @@ class OrderRemoteDataProvider {
}
Future<DC<OrderFailure, PaymentDto>> storePayment(
PaymentRequestDto request,
) async {
PaymentRequestDto request, {
String? idempotencyKey,
}) async {
try {
final response = await _apiClient.post(
ApiPath.payments,
data: request.toJson(),
headers: getAuthorizationHeader(),
headers: _buildHeaders(idempotencyKey: idempotencyKey),
);
if (response.data['success'] == false) {
@@ -178,6 +188,7 @@ class OrderRemoteDataProvider {
Future<DC<OrderFailure, OrderDto>> addItemOrder({
required String id,
required List<AddItemOrderRequestDto> request,
String? idempotencyKey,
}) async {
try {
final response = await _apiClient.post(
@@ -186,7 +197,7 @@ class OrderRemoteDataProvider {
'notes': '',
'order_items': request.map((e) => e.toRequest()).toList(),
},
headers: getAuthorizationHeader(),
headers: _buildHeaders(idempotencyKey: idempotencyKey),
);
if (response.data['success'] == false) {
@@ -209,6 +220,7 @@ class OrderRemoteDataProvider {
required String reason,
String type = "ITEM", // TYPE: ALL, ITEM
required List<OrderItemDto> orderItems,
String? idempotencyKey,
}) async {
try {
final response = await _apiClient.post(
@@ -223,7 +235,7 @@ class OrderRemoteDataProvider {
)
.toList(),
},
headers: getAuthorizationHeader(),
headers: _buildHeaders(idempotencyKey: idempotencyKey),
);
if (response.data['success'] == false) {
@@ -238,14 +250,15 @@ class OrderRemoteDataProvider {
}
Future<DC<OrderFailure, PaymentDto>> createSplitBill(
PaymentSplitBillRequestDto request,
) async {
PaymentSplitBillRequestDto request, {
String? idempotencyKey,
}) async {
log(request.toRequest().toString());
try {
final response = await _apiClient.post(
"${ApiPath.orders}/split-bill",
data: request.toRequest(),
headers: getAuthorizationHeader(),
headers: _buildHeaders(idempotencyKey: idempotencyKey),
);
if (response.data['success'] == false) {
@@ -267,12 +280,13 @@ class OrderRemoteDataProvider {
required String id,
required String reason,
required int refundAmount,
String? idempotencyKey,
}) async {
try {
final response = await _apiClient.post(
'${ApiPath.orders}/$id/refund',
data: {'refund_amount': refundAmount, 'reason': reason},
headers: getAuthorizationHeader(),
headers: _buildHeaders(idempotencyKey: idempotencyKey),
);
if (response.data['success'] == false) {
@@ -114,6 +114,7 @@ class OrderItemDto with _$OrderItemDto {
@JsonKey(name: "created_at") String? createdAt,
@JsonKey(name: "updated_at") String? updatedAt,
@JsonKey(name: "printer_type") String? printerType,
@JsonKey(name: "print_to_checker") bool? printToChecker,
@JsonKey(name: "paid_quantity") int? paidQuantity,
@JsonKey(name: "category_id") String? categoryId,
@JsonKey(name: "category_name") String? categoryName,
@@ -139,6 +140,7 @@ class OrderItemDto with _$OrderItemDto {
createdAt: createdAt != null ? DateTime.parse(createdAt!) : DateTime(1970),
updatedAt: updatedAt != null ? DateTime.parse(updatedAt!) : DateTime(1970),
printerType: printerType ?? '',
printToChecker: printToChecker ?? false,
paidQuantity: paidQuantity ?? 0,
categoryId: categoryId ?? '',
categoryName: categoryName ?? '',
@@ -160,6 +162,7 @@ class OrderItemDto with _$OrderItemDto {
createdAt: orderItem.createdAt.toIso8601String(),
updatedAt: orderItem.updatedAt.toIso8601String(),
printerType: orderItem.printerType,
printToChecker: orderItem.printToChecker,
paidQuantity: orderItem.paidQuantity,
categoryId: orderItem.categoryId,
categoryName: orderItem.categoryName,
@@ -1169,6 +1169,8 @@ mixin _$OrderItemDto {
String? get updatedAt => throw _privateConstructorUsedError;
@JsonKey(name: "printer_type")
String? get printerType => throw _privateConstructorUsedError;
@JsonKey(name: "print_to_checker")
bool? get printToChecker => throw _privateConstructorUsedError;
@JsonKey(name: "paid_quantity")
int? get paidQuantity => throw _privateConstructorUsedError;
@JsonKey(name: "category_id")
@@ -1209,6 +1211,7 @@ abstract class $OrderItemDtoCopyWith<$Res> {
@JsonKey(name: "created_at") String? createdAt,
@JsonKey(name: "updated_at") String? updatedAt,
@JsonKey(name: "printer_type") String? printerType,
@JsonKey(name: "print_to_checker") bool? printToChecker,
@JsonKey(name: "paid_quantity") int? paidQuantity,
@JsonKey(name: "category_id") String? categoryId,
@JsonKey(name: "category_name") String? categoryName,
@@ -1245,6 +1248,7 @@ class _$OrderItemDtoCopyWithImpl<$Res, $Val extends OrderItemDto>
Object? createdAt = freezed,
Object? updatedAt = freezed,
Object? printerType = freezed,
Object? printToChecker = freezed,
Object? paidQuantity = freezed,
Object? categoryId = freezed,
Object? categoryName = freezed,
@@ -1311,6 +1315,10 @@ class _$OrderItemDtoCopyWithImpl<$Res, $Val extends OrderItemDto>
? _value.printerType
: printerType // ignore: cast_nullable_to_non_nullable
as String?,
printToChecker: freezed == printToChecker
? _value.printToChecker
: printToChecker // ignore: cast_nullable_to_non_nullable
as bool?,
paidQuantity: freezed == paidQuantity
? _value.paidQuantity
: paidQuantity // ignore: cast_nullable_to_non_nullable
@@ -1354,6 +1362,7 @@ abstract class _$$OrderItemDtoImplCopyWith<$Res>
@JsonKey(name: "created_at") String? createdAt,
@JsonKey(name: "updated_at") String? updatedAt,
@JsonKey(name: "printer_type") String? printerType,
@JsonKey(name: "print_to_checker") bool? printToChecker,
@JsonKey(name: "paid_quantity") int? paidQuantity,
@JsonKey(name: "category_id") String? categoryId,
@JsonKey(name: "category_name") String? categoryName,
@@ -1389,6 +1398,7 @@ class __$$OrderItemDtoImplCopyWithImpl<$Res>
Object? createdAt = freezed,
Object? updatedAt = freezed,
Object? printerType = freezed,
Object? printToChecker = freezed,
Object? paidQuantity = freezed,
Object? categoryId = freezed,
Object? categoryName = freezed,
@@ -1455,6 +1465,10 @@ class __$$OrderItemDtoImplCopyWithImpl<$Res>
? _value.printerType
: printerType // ignore: cast_nullable_to_non_nullable
as String?,
printToChecker: freezed == printToChecker
? _value.printToChecker
: printToChecker // ignore: cast_nullable_to_non_nullable
as bool?,
paidQuantity: freezed == paidQuantity
? _value.paidQuantity
: paidQuantity // ignore: cast_nullable_to_non_nullable
@@ -1491,6 +1505,7 @@ class _$OrderItemDtoImpl extends _OrderItemDto {
@JsonKey(name: "created_at") this.createdAt,
@JsonKey(name: "updated_at") this.updatedAt,
@JsonKey(name: "printer_type") this.printerType,
@JsonKey(name: "print_to_checker") this.printToChecker,
@JsonKey(name: "paid_quantity") this.paidQuantity,
@JsonKey(name: "category_id") this.categoryId,
@JsonKey(name: "category_name") this.categoryName,
@@ -1554,6 +1569,9 @@ class _$OrderItemDtoImpl extends _OrderItemDto {
@JsonKey(name: "printer_type")
final String? printerType;
@override
@JsonKey(name: "print_to_checker")
final bool? printToChecker;
@override
@JsonKey(name: "paid_quantity")
final int? paidQuantity;
@override
@@ -1565,7 +1583,7 @@ class _$OrderItemDtoImpl extends _OrderItemDto {
@override
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
@@ -1601,6 +1619,8 @@ class _$OrderItemDtoImpl extends _OrderItemDto {
other.updatedAt == updatedAt) &&
(identical(other.printerType, printerType) ||
other.printerType == printerType) &&
(identical(other.printToChecker, printToChecker) ||
other.printToChecker == printToChecker) &&
(identical(other.paidQuantity, paidQuantity) ||
other.paidQuantity == paidQuantity) &&
(identical(other.categoryId, categoryId) ||
@@ -1611,7 +1631,7 @@ class _$OrderItemDtoImpl extends _OrderItemDto {
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
int get hashCode => Object.hashAll([
runtimeType,
id,
orderId,
@@ -1628,10 +1648,11 @@ class _$OrderItemDtoImpl extends _OrderItemDto {
createdAt,
updatedAt,
printerType,
printToChecker,
paidQuantity,
categoryId,
categoryName,
);
]);
/// Create a copy of OrderItemDto
/// 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: "updated_at") final String? updatedAt,
@JsonKey(name: "printer_type") final String? printerType,
@JsonKey(name: "print_to_checker") final bool? printToChecker,
@JsonKey(name: "paid_quantity") final int? paidQuantity,
@JsonKey(name: "category_id") final String? categoryId,
@JsonKey(name: "category_name") final String? categoryName,
@@ -1719,6 +1741,9 @@ abstract class _OrderItemDto extends OrderItemDto {
@JsonKey(name: "printer_type")
String? get printerType;
@override
@JsonKey(name: "print_to_checker")
bool? get printToChecker;
@override
@JsonKey(name: "paid_quantity")
int? get paidQuantity;
@override
@@ -109,6 +109,7 @@ _$OrderItemDtoImpl _$$OrderItemDtoImplFromJson(Map<String, dynamic> json) =>
createdAt: json['created_at'] as String?,
updatedAt: json['updated_at'] as String?,
printerType: json['printer_type'] as String?,
printToChecker: json['print_to_checker'] as bool?,
paidQuantity: (json['paid_quantity'] as num?)?.toInt(),
categoryId: json['category_id'] as String?,
categoryName: json['category_name'] as String?,
@@ -131,6 +132,7 @@ Map<String, dynamic> _$$OrderItemDtoImplToJson(_$OrderItemDtoImpl instance) =>
'created_at': instance.createdAt,
'updated_at': instance.updatedAt,
'printer_type': instance.printerType,
'print_to_checker': instance.printToChecker,
'paid_quantity': instance.paidQuantity,
'category_id': instance.categoryId,
'category_name': instance.categoryName,
@@ -110,6 +110,7 @@ class OrderRepository implements IOrderRepository {
Future<Either<OrderFailure, Order>> addItemOrder({
required String id,
required List<AddItemOrderRequest> request,
String? idempotencyKey,
}) async {
try {
final result = await _dataProvider.addItemOrder(
@@ -117,6 +118,7 @@ class OrderRepository implements IOrderRepository {
request: request
.map((e) => AddItemOrderRequestDto.fromDomain(e))
.toList(),
idempotencyKey: idempotencyKey,
);
if (result.hasError) {
@@ -134,10 +136,12 @@ class OrderRepository implements IOrderRepository {
@override
Future<Either<OrderFailure, Payment>> createPayment({
required PaymentRequest request,
String? idempotencyKey,
}) async {
try {
final result = await _dataProvider.storePayment(
PaymentRequestDto.fromDomain(request),
idempotencyKey: idempotencyKey,
);
if (result.hasError) {
@@ -158,6 +162,7 @@ class OrderRepository implements IOrderRepository {
required String reason,
String type = "ITEM",
required List<OrderItem> orderItems,
String? idempotencyKey,
}) async {
try {
final result = await _dataProvider.voidOrder(
@@ -165,6 +170,7 @@ class OrderRepository implements IOrderRepository {
reason: reason,
type: type,
orderItems: orderItems.map((e) => OrderItemDto.fromDomain(e)).toList(),
idempotencyKey: idempotencyKey,
);
if (result.hasError) {
@@ -180,11 +186,13 @@ class OrderRepository implements IOrderRepository {
@override
Future<Either<OrderFailure, Payment>> createSplitBill(
PaymentSplitBillRequest request,
) async {
PaymentSplitBillRequest request, {
String? idempotencyKey,
}) async {
try {
final result = await _dataProvider.createSplitBill(
PaymentSplitBillRequestDto.fromDomain(request),
idempotencyKey: idempotencyKey,
);
if (result.hasError) {
@@ -204,12 +212,14 @@ class OrderRepository implements IOrderRepository {
required String id,
required String reason,
required int refundAmount,
String? idempotencyKey,
}) async {
try {
final result = await _dataProvider.refundPayment(
id: id,
reason: reason,
refundAmount: refundAmount,
idempotencyKey: idempotencyKey,
);
if (result.hasError) {
return left(result.error!);
@@ -460,78 +460,78 @@ class PrinterRepository implements IPrinterRepository {
required Order order,
}) async {
return await _globalPrintLock.synchronized(() async {
try {
final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser();
try {
final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser();
// Struck for customer
if (order.totalPaid > 0) {
final result = await _printReceipt(
// Struck for customer
if (order.totalPaid > 0) {
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,
outlet: outlet,
cashieName: user.name,
);
if (result.isLeft()) return result; // Jika gagal, return error
}
if (cashierResult.isLeft()) return cashierResult;
// Struck for cashier
final cashierResult = await _printCashier(
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(
// Struck for checker
final checkerResult = await _printChecker(
order: order,
outlet: outlet,
cashieName: user.name,
);
if (kitchenResult.isLeft()) return kitchenResult;
}
if (checkerResult.isLeft()) return checkerResult;
// 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,
// 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 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
}
@@ -540,69 +540,69 @@ class PrinterRepository implements IPrinterRepository {
required Order order,
}) async {
return await _globalPrintLock.synchronized(() async {
try {
final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser();
try {
final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser();
// Struck for cashier
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(
// Struck for cashier
final cashierResult = await _printCashier(
order: order,
outlet: outlet,
cashieName: user.name,
);
if (kitchenResult.isLeft()) return kitchenResult;
}
if (cashierResult.isLeft()) return cashierResult;
// Struck for bar if exist product bar
final itemsBar = order.orderItems.where(
(item) => item.printerType == 'bar',
);
if (itemsBar.isNotEmpty) {
final barResult = await _printBar(
// Struck for checker
final checkerResult = await _printChecker(
order: order,
outlet: outlet,
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
}
@@ -611,9 +611,9 @@ class PrinterRepository implements IPrinterRepository {
required Order order,
}) async {
return await _globalPrintLock.synchronized(() async {
final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser();
return _printCashier(order: order, outlet: outlet, cashieName: user.name);
final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser();
return _printCashier(order: order, outlet: outlet, cashieName: user.name);
});
}
@@ -622,9 +622,9 @@ class PrinterRepository implements IPrinterRepository {
required Order order,
}) async {
return await _globalPrintLock.synchronized(() async {
final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser();
return _printVoid(order: order, outlet: outlet, cashieName: user.name);
final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser();
return _printVoid(order: order, outlet: outlet, cashieName: user.name);
});
}
@@ -633,9 +633,13 @@ class PrinterRepository implements IPrinterRepository {
required Order order,
}) async {
return await _globalPrintLock.synchronized(() async {
final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser();
return _printSplitBill(order: order, outlet: outlet, cashieName: user.name);
final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser();
return _printSplitBill(
order: order,
outlet: outlet,
cashieName: user.name,
);
});
}
@@ -644,9 +648,9 @@ class PrinterRepository implements IPrinterRepository {
required Order order,
}) async {
return await _globalPrintLock.synchronized(() async {
final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser();
return _printReceipt(order: order, outlet: outlet, cashieName: user.name);
final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser();
return _printReceipt(order: order, outlet: outlet, cashieName: user.name);
});
}
@@ -1075,9 +1079,9 @@ class PrinterRepository implements IPrinterRepository {
required Order order,
}) async {
return await _globalPrintLock.synchronized(() async {
final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser();
return _printBar(order: order, outlet: outlet, cashieName: user.name);
final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser();
return _printBar(order: order, outlet: outlet, cashieName: user.name);
});
}
@@ -1086,9 +1090,9 @@ class PrinterRepository implements IPrinterRepository {
required Order order,
}) async {
return await _globalPrintLock.synchronized(() async {
final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser();
return _printChecker(order: order, outlet: outlet, cashieName: user.name);
final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser();
return _printChecker(order: order, outlet: outlet, cashieName: user.name);
});
}
@@ -1097,9 +1101,9 @@ class PrinterRepository implements IPrinterRepository {
required Order order,
}) async {
return await _globalPrintLock.synchronized(() async {
final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser();
return _printKitchen(order: order, outlet: outlet, cashieName: user.name);
final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser();
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: "image_url") String? imageUrl,
@JsonKey(name: "printer_type") String? printerType,
@JsonKey(name: "print_to_checker") bool? printToChecker,
@JsonKey(name: "metadata") Map<String, dynamic>? metadata,
@JsonKey(name: "is_active") bool? isActive,
@JsonKey(name: "created_at") String? createdAt,
@@ -63,6 +64,7 @@ class ProductDto with _$ProductDto {
businessType: businessType ?? '',
imageUrl: imageUrl ?? '',
printerType: printerType ?? '',
printToChecker: printToChecker ?? false,
metadata: metadata ?? {},
isActive: isActive ?? false,
createdAt: createdAt ?? '',
@@ -82,6 +84,7 @@ class ProductDto with _$ProductDto {
'business_type': businessType,
'image_url': imageUrl,
'printer_type': printerType,
'print_to_checker': printToChecker == true ? 1 : 0,
'metadata': metadata != null ? jsonEncode(metadata) : null,
'is_active': isActive == true ? 1 : 0,
'created_at': createdAt,
@@ -103,6 +106,9 @@ class ProductDto with _$ProductDto {
businessType: map['business_type'] as String?,
imageUrl: map['image_url'] 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
? jsonDecode(map['metadata'] as String) as Map<String, dynamic>
: null,
@@ -125,6 +131,7 @@ class ProductDto with _$ProductDto {
businessType: product.businessType,
imageUrl: product.imageUrl,
printerType: product.printerType,
printToChecker: product.printToChecker,
metadata: product.metadata,
isActive: product.isActive,
createdAt: product.createdAt,
@@ -321,6 +321,8 @@ mixin _$ProductDto {
String? get imageUrl => throw _privateConstructorUsedError;
@JsonKey(name: "printer_type")
String? get printerType => throw _privateConstructorUsedError;
@JsonKey(name: "print_to_checker")
bool? get printToChecker => throw _privateConstructorUsedError;
@JsonKey(name: "metadata")
Map<String, dynamic>? get metadata => throw _privateConstructorUsedError;
@JsonKey(name: "is_active")
@@ -361,6 +363,7 @@ abstract class $ProductDtoCopyWith<$Res> {
@JsonKey(name: "business_type") String? businessType,
@JsonKey(name: "image_url") String? imageUrl,
@JsonKey(name: "printer_type") String? printerType,
@JsonKey(name: "print_to_checker") bool? printToChecker,
@JsonKey(name: "metadata") Map<String, dynamic>? metadata,
@JsonKey(name: "is_active") bool? isActive,
@JsonKey(name: "created_at") String? createdAt,
@@ -395,6 +398,7 @@ class _$ProductDtoCopyWithImpl<$Res, $Val extends ProductDto>
Object? businessType = freezed,
Object? imageUrl = freezed,
Object? printerType = freezed,
Object? printToChecker = freezed,
Object? metadata = freezed,
Object? isActive = freezed,
Object? createdAt = freezed,
@@ -447,6 +451,10 @@ class _$ProductDtoCopyWithImpl<$Res, $Val extends ProductDto>
? _value.printerType
: printerType // ignore: cast_nullable_to_non_nullable
as String?,
printToChecker: freezed == printToChecker
? _value.printToChecker
: printToChecker // ignore: cast_nullable_to_non_nullable
as bool?,
metadata: freezed == metadata
? _value.metadata
: metadata // ignore: cast_nullable_to_non_nullable
@@ -494,6 +502,7 @@ abstract class _$$ProductDtoImplCopyWith<$Res>
@JsonKey(name: "business_type") String? businessType,
@JsonKey(name: "image_url") String? imageUrl,
@JsonKey(name: "printer_type") String? printerType,
@JsonKey(name: "print_to_checker") bool? printToChecker,
@JsonKey(name: "metadata") Map<String, dynamic>? metadata,
@JsonKey(name: "is_active") bool? isActive,
@JsonKey(name: "created_at") String? createdAt,
@@ -527,6 +536,7 @@ class __$$ProductDtoImplCopyWithImpl<$Res>
Object? businessType = freezed,
Object? imageUrl = freezed,
Object? printerType = freezed,
Object? printToChecker = freezed,
Object? metadata = freezed,
Object? isActive = freezed,
Object? createdAt = freezed,
@@ -579,6 +589,10 @@ class __$$ProductDtoImplCopyWithImpl<$Res>
? _value.printerType
: printerType // ignore: cast_nullable_to_non_nullable
as String?,
printToChecker: freezed == printToChecker
? _value.printToChecker
: printToChecker // ignore: cast_nullable_to_non_nullable
as bool?,
metadata: freezed == metadata
? _value._metadata
: metadata // ignore: cast_nullable_to_non_nullable
@@ -619,6 +633,7 @@ class _$ProductDtoImpl extends _ProductDto {
@JsonKey(name: "business_type") this.businessType,
@JsonKey(name: "image_url") this.imageUrl,
@JsonKey(name: "printer_type") this.printerType,
@JsonKey(name: "print_to_checker") this.printToChecker,
@JsonKey(name: "metadata") final Map<String, dynamic>? metadata,
@JsonKey(name: "is_active") this.isActive,
@JsonKey(name: "created_at") this.createdAt,
@@ -664,6 +679,9 @@ class _$ProductDtoImpl extends _ProductDto {
@override
@JsonKey(name: "printer_type")
final String? printerType;
@override
@JsonKey(name: "print_to_checker")
final bool? printToChecker;
final Map<String, dynamic>? _metadata;
@override
@JsonKey(name: "metadata")
@@ -697,7 +715,7 @@ class _$ProductDtoImpl extends _ProductDto {
@override
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
@@ -722,6 +740,8 @@ class _$ProductDtoImpl extends _ProductDto {
other.imageUrl == imageUrl) &&
(identical(other.printerType, printerType) ||
other.printerType == printerType) &&
(identical(other.printToChecker, printToChecker) ||
other.printToChecker == printToChecker) &&
const DeepCollectionEquality().equals(other._metadata, _metadata) &&
(identical(other.isActive, isActive) ||
other.isActive == isActive) &&
@@ -747,6 +767,7 @@ class _$ProductDtoImpl extends _ProductDto {
businessType,
imageUrl,
printerType,
printToChecker,
const DeepCollectionEquality().hash(_metadata),
isActive,
createdAt,
@@ -781,6 +802,7 @@ abstract class _ProductDto extends ProductDto {
@JsonKey(name: "business_type") final String? businessType,
@JsonKey(name: "image_url") final String? imageUrl,
@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: "is_active") final bool? isActive,
@JsonKey(name: "created_at") final String? createdAt,
@@ -826,6 +848,9 @@ abstract class _ProductDto extends ProductDto {
@JsonKey(name: "printer_type")
String? get printerType;
@override
@JsonKey(name: "print_to_checker")
bool? get printToChecker;
@override
@JsonKey(name: "metadata")
Map<String, dynamic>? get metadata;
@override
@@ -40,6 +40,7 @@ _$ProductDtoImpl _$$ProductDtoImplFromJson(Map<String, dynamic> json) =>
businessType: json['business_type'] as String?,
imageUrl: json['image_url'] as String?,
printerType: json['printer_type'] as String?,
printToChecker: json['print_to_checker'] as bool?,
metadata: json['metadata'] as Map<String, dynamic>?,
isActive: json['is_active'] as bool?,
createdAt: json['created_at'] as String?,
@@ -62,6 +63,7 @@ Map<String, dynamic> _$$ProductDtoImplToJson(_$ProductDtoImpl instance) =>
'business_type': instance.businessType,
'image_url': instance.imageUrl,
'printer_type': instance.printerType,
'print_to_checker': instance.printToChecker,
'metadata': instance.metadata,
'is_active': instance.isActive,
'created_at': instance.createdAt,
+2 -3
View File
@@ -44,9 +44,8 @@ void main() async {
debugPrint = (message, {wrapWidth}) => '';
}
await configureDependencies(
kReleaseMode ? Environment.prod : Environment.dev,
);
const String env = String.fromEnvironment('ENV', defaultValue: 'dev');
await configureDependencies(env);
// Inisialisasi FCM setelah DI siap
await getIt<FcmService>().initialize();
@@ -28,18 +28,20 @@ class OrderVoidConfirmDialog extends StatelessWidget {
children: [
Expanded(
child: AppElevatedButton.outlined(
onPressed: () => context.maybePop(),
onPressed: state.isSubmitting ? null : () => context.maybePop(),
label: 'Batal',
),
),
const SpaceWidth(16),
Expanded(
child: AppElevatedButton.filled(
onPressed: () {
context.read<VoidFormBloc>().add(
const VoidFormEvent.submitted(),
);
},
onPressed: state.isSubmitting
? null
: () {
context.read<VoidFormBloc>().add(
const VoidFormEvent.submitted(),
);
},
label: state.voidType.isAll ? 'Void Pesanan' : 'Void Item',
isLoading: state.isSubmitting,
),
@@ -117,7 +117,7 @@ class PrintUi {
bytes += builder.separator();
// 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();
bytes += builder.itemText('${item.quantity} $name');
if (item.productVariantName.isNotEmpty) {
+6 -6
View File
@@ -852,10 +852,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
url: "https://pub.dev"
source: hosted
version: "1.17.0"
version: "1.18.0"
mime:
dependency: transitive
description:
@@ -1337,10 +1337,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
url: "https://pub.dev"
source: hosted
version: "0.7.10"
version: "0.7.11"
time:
dependency: transitive
description:
@@ -1374,7 +1374,7 @@ packages:
source: hosted
version: "1.4.0"
uuid:
dependency: transitive
dependency: "direct main"
description:
name: uuid
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
@@ -1502,5 +1502,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.9.0-0 <4.0.0"
dart: ">=3.10.0-0 <4.0.0"
flutter: ">=3.29.0"
+2 -1
View File
@@ -3,7 +3,7 @@ description: "A new Flutter project."
publish_to: "none"
version: 1.0.7+13
version: 1.0.11+17
environment:
sdk: ^3.8.1
@@ -49,6 +49,7 @@ dependencies:
table_calendar: ^3.1.2
synchronized: ^3.4.0
collection: ^1.19.1
uuid: ^4.5.1
dev_dependencies:
flutter_test: