Compare commits

..
55 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
Efril 9a5f0c7415 fix printer 2026-05-27 19:51:43 +07:00
Efril d345294a2f update printer bar and kitchen 2026-05-27 16:19:11 +07:00
Efril beb86f6259 update printer checker 2026-05-27 16:13:12 +07:00
efrilm ea29c62af1 update build number 2026-05-26 12:14:30 +07:00
efrilm c8a84ffe5f update kitchen printer 2026-05-26 12:14:02 +07:00
Efril 539c70be17 update 2026-05-25 23:35:24 +07:00
Efril c45848bcf2 update printer 2026-05-25 23:33:49 +07:00
Efril 6d9377f4ab update printer 2026-05-25 20:35:40 +07:00
Efril 56e720e253 update checker print 2026-05-25 19:34:31 +07:00
Efril c7ed45a374 fix printer 2026-05-24 00:59:02 +07:00
Efril a0610459bf update login with device info 2026-05-10 22:51:04 +07:00
Efril 842b7a5041 fix fcm 2026-05-07 18:03:41 +07:00
Efril dc09aabbe8 setup fcm 2026-05-07 17:44:03 +07:00
Efril cdc65f8f8f update success add item user before customer name 2026-04-29 12:46:52 +07:00
Efril e7260ea0e2 fix success add item order 2026-04-27 17:39:15 +07:00
Efril b173654a9c update print ui and report dashboard 2026-04-27 17:28:34 +07:00
Efril 699ca4cfb7 handler error print 2026-04-27 12:53:55 +07:00
Efril d790262fd4 update version 2026-04-26 22:33:33 +07:00
Efril 60fec68a92 fix void 2026-04-26 22:32:48 +07:00
Efril ab2d7d7be5 fix void 2026-04-26 22:31:26 +07:00
Efril 6ac88b92ae fix add order item 2026-04-26 22:27:44 +07:00
Efril 9e4e2ebbd1 fix printer fc 2026-04-26 22:15:50 +07:00
Efril 3513c63259 fix qty split bill 2026-04-10 09:50:44 +07:00
Efril 46dfb7e44e update print all segment 2026-04-03 17:44:04 +07:00
Efril a0db13feb4 update version 2026-03-04 23:33:08 +07:00
Efril f626b18d31 fix date range picker 2026-03-04 23:32:47 +07:00
Efril 200f996d3e fix split bill 2026-03-04 23:30:52 +07:00
Efril a739e1c29e update payment page 2026-03-04 22:43:18 +07:00
Efril 4481d24375 update 2026-03-03 19:35:11 +07:00
Efril 59d8f06d75 update printer 2026-03-03 19:11:29 +07:00
Efril e625bd3d2a const constructure 2026-03-01 20:02:12 +07:00
Efril 5058f99fe0 update printer 2026-03-01 19:37:23 +07:00
Efril f7cafeb583 change package calendar 2026-03-01 19:11:08 +07:00
efrilm 37d747c0ba update report 2025-11-13 17:47:00 +07:00
efrilm e3b9444a08 fix printer 2025-11-13 15:57:43 +07:00
efrilm d8a28c084d update printer bar 2025-11-13 13:04:32 +07:00
efrilm d3974b8eda fix printer 2025-11-13 12:56:28 +07:00
efrilm 97313371c3 fix table 2025-11-13 12:45:09 +07:00
efrilm 6fa45c8ac0 fix outlet, fix category, fix printer 2025-11-13 12:01:24 +07:00
efrilm b2b2e2b111 logout 2025-11-10 17:23:26 +07:00
efrilm 0a8f8d93bb Sync Page 2025-11-10 16:56:12 +07:00
efrilm f5256fb33d split struct 2025-11-10 13:28:18 +07:00
efrilm c5673a147c void struck 2025-11-10 13:06:06 +07:00
efrilm af182b6f5a print payment 2025-11-07 17:08:57 +07:00
efrilm d78b6165c2 print bill 2025-11-07 16:59:46 +07:00
efrilm 0886e8c912 print order add item 2025-11-07 16:45:36 +07:00
223 changed files with 8728 additions and 1790 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"]
}
]
}
+209 -10
View File
@@ -1,16 +1,215 @@
# apskel_pos_flutter_v2
# Apskel POS Flutter
A new Flutter project.
Aplikasi Point of Sale (POS) berbasis Flutter untuk manajemen kasir restoran. Mendukung manajemen order, produk, meja, pelanggan, pembayaran, printer bluetooth/network, analitik, dan push notification via FCM.
## Getting Started
---
This project is a starting point for a Flutter application.
## Fitur Utama
A few resources to get you started if this is your first Flutter project:
- **Autentikasi** — Login dengan email & password, logout, session management
- **Order Management** — Buat, kelola, dan proses order
- **Produk & Kategori** — Manajemen menu dan kategori produk
- **Meja** — Manajemen meja restoran
- **Pelanggan** — Data pelanggan dan riwayat transaksi
- **Checkout & Pembayaran** — Proses checkout dengan berbagai metode pembayaran
- **Split Bill** — Pembagian tagihan
- **Void & Refund** — Pembatalan dan pengembalian transaksi
- **Printer** — Cetak struk via Bluetooth dan Network printer
- **Analitik** — Dashboard, laporan penjualan, produk, kategori, payment method, profit/loss, inventory
- **Sinkronisasi** — Sinkronisasi data offline/online
- **Push Notification (FCM)** — Notifikasi real-time via Firebase Cloud Messaging
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
---
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
## Tech Stack
| Kategori | Library |
|---|---|
| State Management | `flutter_bloc` + `bloc` |
| Dependency Injection | `get_it` + `injectable` |
| Navigation | `auto_route` |
| HTTP Client | `dio` |
| Local Database | `sqflite` |
| Local Storage | `shared_preferences` |
| Firebase | `firebase_core`, `firebase_crashlytics`, `firebase_messaging` |
| Push Notification | `flutter_local_notifications` |
| Device Info | `device_info_plus`, `package_info_plus` |
| Code Generation | `freezed`, `json_serializable` |
| Printer | `print_bluetooth_thermal`, `flutter_esc_pos_network` |
| Chart | `fl_chart` |
| Connectivity | `connectivity_plus` |
---
## Arsitektur
Project menggunakan **Clean Architecture** dengan 4 layer:
```
lib/
├── application/ # BLoC — state management per fitur
├── domain/ # Entity, repository interface, failure
├── infrastructure/ # Implementasi repository, DTO, datasource
├── presentation/ # UI — pages, components, router
└── common/ # Shared utilities, DI modules, service, theme
```
### Struktur per Fitur
```
feature/
├── application/
│ └── feature_bloc.dart # BLoC
├── domain/
│ ├── entities/ # Domain model (freezed)
│ ├── repositories/ # Interface repository
│ └── failures/ # Failure types
└── infrastructure/
├── datasources/
│ ├── remote_data_provider.dart
│ └── local_data_provider.dart
├── dtos/ # Data Transfer Object (json_serializable)
└── repositories/ # Implementasi repository
```
---
## Setup & Menjalankan
### Prasyarat
- Flutter SDK `^3.8.1`
- Dart SDK `^3.8.1`
- Android SDK / Xcode (untuk iOS)
- Firebase project yang sudah dikonfigurasi
### Instalasi
```bash
# Clone repository
git clone <repo-url>
cd apskel-pos-flutter-v2
# Install dependencies
flutter pub get
# Generate kode (freezed, injectable, auto_route, json_serializable)
dart run build_runner build --delete-conflicting-outputs
```
### Menjalankan App
```bash
# Development
flutter run
# Release
flutter run --release
```
App otomatis menggunakan environment `dev` saat debug dan `prod` saat release.
---
## Environment
Konfigurasi environment ada di `lib/env.dart`:
| Environment | Base URL | Database |
|---|---|---|
| `dev` | `https://api-pos.apskel.id` | `apskel_pos_dev.db` |
| `prod` | `https://api-pos.apskel.id` | `apskel_pos_dev.db` |
Environment dipilih otomatis di `main.dart`:
```dart
await configureDependencies(
kReleaseMode ? Environment.prod : Environment.dev,
);
```
---
## Dependency Injection
DI menggunakan `get_it` + `injectable`. Semua service, repository, dan BLoC didaftarkan via annotation.
### Modul DI
| File | Isi |
|---|---|
| `di_firebase.dart` | `FirebaseMessaging`, `FlutterLocalNotificationsPlugin`, `DeviceInfoPlugin`, `PackageInfo` |
| `di_dio.dart` | `Dio` HTTP client |
| `di_shared_preferences.dart` | `SharedPreferences` |
| `di_database.dart` | `DatabaseHelper` (SQLite) |
| `di_auto_route.dart` | `AppRouter` |
| `di_connectivity.dart` | `Connectivity` |
Setelah menambah atau mengubah class yang menggunakan annotation injectable, jalankan:
```bash
dart run build_runner build --delete-conflicting-outputs
```
---
## Firebase & FCM
### Setup
Firebase sudah dikonfigurasi di `android/app/google-services.json`. Inisialisasi dilakukan di `main.dart` sebelum app berjalan.
### FCM Service
`FcmService` (`lib/common/service/fcm_service.dart`) menangani:
- Request permission notifikasi (Android 13+ / iOS)
- Ambil dan log FCM token
- Foreground notification via `flutter_local_notifications`
- Background & terminated message handler
- Subscribe/unsubscribe topic
FCM token dikirim ke server saat login bersama device info.
### Login Payload
Saat login, app mengirim data berikut ke API:
```json
{
"email": "user@example.com",
"password": "secret",
"fcm_token": "dXj3k9...",
"device_id": "abc123",
"device_name": "Samsung Galaxy Tab S8",
"device_type": "tablet",
"platform": "android",
"app_version": "1.0.4+9",
"os_version": "Android 13 (SDK 33)"
}
```
Nilai valid: `device_type``mobile | tablet | desktop`, `platform``android | ios | web`
---
## Printer
Mendukung dua jenis printer:
- **Bluetooth** — via `print_bluetooth_thermal`
- **Network (LAN)** — via `flutter_esc_pos_network`
---
## Orientasi Layar
App dikunci ke mode **landscape** (kiri & kanan) karena didesain untuk tablet POS.
---
## Catatan Development
- Jangan edit `injection.config.dart` dan file `*.freezed.dart` / `*.g.dart` secara manual — file tersebut di-generate otomatis
- Gunakan `log()` dari `dart:developer` untuk logging, bukan `print()`
- `print()` dinonaktifkan di release mode
+3
View File
@@ -4,6 +4,9 @@ linter:
rules:
sort_pub_dependencies: false
prefer_relative_imports: true
prefer_const_constructors: true
prefer_const_literals_to_create_immutables: true
prefer_const_declarations: true
analyzer:
errors:
+3
View File
@@ -14,6 +14,7 @@ android {
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
isCoreLibraryDesugaringEnabled = true
}
kotlinOptions {
@@ -45,6 +46,8 @@ flutter {
}
dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
// Import the Firebase BoM
implementation(platform("com.google.firebase:firebase-bom:34.4.0"))
+9
View File
@@ -10,6 +10,8 @@
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<!-- Izin khusus untuk akses foto (media images) di Android 33 ke atas -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<!-- Izin notifikasi untuk Android 13+ -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:label="Apskel POS"
android:name="${applicationName}"
@@ -41,6 +43,13 @@
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<!-- FCM: default notification icon dan channel -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_notification" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="high_importance_channel" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
Binary file not shown.

After

Width:  |  Height:  |  Size: 846 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

+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
View File
@@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:
+4 -4
View File
@@ -33,16 +33,16 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
state.copyWith(
failureOption: optionOf(f),
status: token
? AuthStatus.authenticated()
: AuthStatus.unauthenticated(),
? const AuthStatus.authenticated()
: const AuthStatus.unauthenticated(),
),
),
(user) => emit(
state.copyWith(
user: user,
status: token
? AuthStatus.authenticated()
: AuthStatus.unauthenticated(),
? const AuthStatus.authenticated()
: const AuthStatus.unauthenticated(),
),
),
);
@@ -5,6 +5,8 @@ import 'package:dartz/dartz.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart';
import '../../../common/service/device_info_service.dart';
import '../../../common/service/fcm_service.dart';
import '../../../domain/auth/auth.dart';
part 'login_form_event.dart';
@@ -14,7 +16,14 @@ part 'login_form_bloc.freezed.dart';
@injectable
class LoginFormBloc extends Bloc<LoginFormEvent, LoginFormState> {
final IAuthRepository _authRepository;
LoginFormBloc(this._authRepository) : super(LoginFormState.initial()) {
final FcmService _fcmService;
final DeviceInfoService _deviceInfoService;
LoginFormBloc(
this._authRepository,
this._fcmService,
this._deviceInfoService,
) : super(LoginFormState.initial()) {
on<LoginFormEvent>(_onLoginFormEvent);
}
@@ -43,9 +52,14 @@ class LoginFormBloc extends Bloc<LoginFormEvent, LoginFormState> {
);
if (emailValid && passwordValid) {
final fcmToken = await _fcmService.getToken() ?? '';
final devicePayload = await _deviceInfoService.getDevicePayload();
failureOrLogin = await _authRepository.login(
email: state.email,
password: state.password,
fcmToken: fcmToken,
devicePayload: devicePayload,
);
emit(
@@ -0,0 +1,38 @@
import 'package:bloc/bloc.dart';
import 'package:dartz/dartz.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart';
import '../../../domain/auth/auth.dart';
part 'logout_event.dart';
part 'logout_state.dart';
part 'logout_bloc.freezed.dart';
@injectable
class LogoutBloc extends Bloc<LogoutEvent, LogoutState> {
final IAuthRepository _authRepository;
LogoutBloc(this._authRepository) : super(LogoutState.initial()) {
on<LogoutEvent>(_onLogoutEvent);
}
Future<void> _onLogoutEvent(LogoutEvent event, Emitter<LogoutState> emit) {
return event.map(
logout: (e) async {
Either<AuthFailure, Unit>? failureOrSuccess;
emit(
state.copyWith(isLoggingOut: true, logoutFailureOrSuccess: none()),
);
failureOrSuccess = await _authRepository.logout();
emit(
state.copyWith(
isLoggingOut: false,
logoutFailureOrSuccess: optionOf(failureOrSuccess),
),
);
},
);
}
}
@@ -0,0 +1,332 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'logout_bloc.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
);
/// @nodoc
mixin _$LogoutEvent {
@optionalTypeArgs
TResult when<TResult extends Object?>({required TResult Function() logout}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({TResult? Function()? logout}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? logout,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Logout value) logout,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Logout value)? logout,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Logout value)? logout,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $LogoutEventCopyWith<$Res> {
factory $LogoutEventCopyWith(
LogoutEvent value,
$Res Function(LogoutEvent) then,
) = _$LogoutEventCopyWithImpl<$Res, LogoutEvent>;
}
/// @nodoc
class _$LogoutEventCopyWithImpl<$Res, $Val extends LogoutEvent>
implements $LogoutEventCopyWith<$Res> {
_$LogoutEventCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of LogoutEvent
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
abstract class _$$LogoutImplCopyWith<$Res> {
factory _$$LogoutImplCopyWith(
_$LogoutImpl value,
$Res Function(_$LogoutImpl) then,
) = __$$LogoutImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$LogoutImplCopyWithImpl<$Res>
extends _$LogoutEventCopyWithImpl<$Res, _$LogoutImpl>
implements _$$LogoutImplCopyWith<$Res> {
__$$LogoutImplCopyWithImpl(
_$LogoutImpl _value,
$Res Function(_$LogoutImpl) _then,
) : super(_value, _then);
/// Create a copy of LogoutEvent
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$LogoutImpl implements _Logout {
const _$LogoutImpl();
@override
String toString() {
return 'LogoutEvent.logout()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$LogoutImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({required TResult Function() logout}) {
return logout();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({TResult? Function()? logout}) {
return logout?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? logout,
required TResult orElse(),
}) {
if (logout != null) {
return logout();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Logout value) logout,
}) {
return logout(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Logout value)? logout,
}) {
return logout?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Logout value)? logout,
required TResult orElse(),
}) {
if (logout != null) {
return logout(this);
}
return orElse();
}
}
abstract class _Logout implements LogoutEvent {
const factory _Logout() = _$LogoutImpl;
}
/// @nodoc
mixin _$LogoutState {
Option<Either<AuthFailure, Unit>> get logoutFailureOrSuccess =>
throw _privateConstructorUsedError;
bool get isLoggingOut => throw _privateConstructorUsedError;
/// Create a copy of LogoutState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$LogoutStateCopyWith<LogoutState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $LogoutStateCopyWith<$Res> {
factory $LogoutStateCopyWith(
LogoutState value,
$Res Function(LogoutState) then,
) = _$LogoutStateCopyWithImpl<$Res, LogoutState>;
@useResult
$Res call({
Option<Either<AuthFailure, Unit>> logoutFailureOrSuccess,
bool isLoggingOut,
});
}
/// @nodoc
class _$LogoutStateCopyWithImpl<$Res, $Val extends LogoutState>
implements $LogoutStateCopyWith<$Res> {
_$LogoutStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of LogoutState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? logoutFailureOrSuccess = null,
Object? isLoggingOut = null,
}) {
return _then(
_value.copyWith(
logoutFailureOrSuccess: null == logoutFailureOrSuccess
? _value.logoutFailureOrSuccess
: logoutFailureOrSuccess // ignore: cast_nullable_to_non_nullable
as Option<Either<AuthFailure, Unit>>,
isLoggingOut: null == isLoggingOut
? _value.isLoggingOut
: isLoggingOut // ignore: cast_nullable_to_non_nullable
as bool,
)
as $Val,
);
}
}
/// @nodoc
abstract class _$$LogoutStateImplCopyWith<$Res>
implements $LogoutStateCopyWith<$Res> {
factory _$$LogoutStateImplCopyWith(
_$LogoutStateImpl value,
$Res Function(_$LogoutStateImpl) then,
) = __$$LogoutStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({
Option<Either<AuthFailure, Unit>> logoutFailureOrSuccess,
bool isLoggingOut,
});
}
/// @nodoc
class __$$LogoutStateImplCopyWithImpl<$Res>
extends _$LogoutStateCopyWithImpl<$Res, _$LogoutStateImpl>
implements _$$LogoutStateImplCopyWith<$Res> {
__$$LogoutStateImplCopyWithImpl(
_$LogoutStateImpl _value,
$Res Function(_$LogoutStateImpl) _then,
) : super(_value, _then);
/// Create a copy of LogoutState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? logoutFailureOrSuccess = null,
Object? isLoggingOut = null,
}) {
return _then(
_$LogoutStateImpl(
logoutFailureOrSuccess: null == logoutFailureOrSuccess
? _value.logoutFailureOrSuccess
: logoutFailureOrSuccess // ignore: cast_nullable_to_non_nullable
as Option<Either<AuthFailure, Unit>>,
isLoggingOut: null == isLoggingOut
? _value.isLoggingOut
: isLoggingOut // ignore: cast_nullable_to_non_nullable
as bool,
),
);
}
}
/// @nodoc
class _$LogoutStateImpl implements _LogoutState {
_$LogoutStateImpl({
required this.logoutFailureOrSuccess,
this.isLoggingOut = false,
});
@override
final Option<Either<AuthFailure, Unit>> logoutFailureOrSuccess;
@override
@JsonKey()
final bool isLoggingOut;
@override
String toString() {
return 'LogoutState(logoutFailureOrSuccess: $logoutFailureOrSuccess, isLoggingOut: $isLoggingOut)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$LogoutStateImpl &&
(identical(other.logoutFailureOrSuccess, logoutFailureOrSuccess) ||
other.logoutFailureOrSuccess == logoutFailureOrSuccess) &&
(identical(other.isLoggingOut, isLoggingOut) ||
other.isLoggingOut == isLoggingOut));
}
@override
int get hashCode =>
Object.hash(runtimeType, logoutFailureOrSuccess, isLoggingOut);
/// Create a copy of LogoutState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$LogoutStateImplCopyWith<_$LogoutStateImpl> get copyWith =>
__$$LogoutStateImplCopyWithImpl<_$LogoutStateImpl>(this, _$identity);
}
abstract class _LogoutState implements LogoutState {
factory _LogoutState({
required final Option<Either<AuthFailure, Unit>> logoutFailureOrSuccess,
final bool isLoggingOut,
}) = _$LogoutStateImpl;
@override
Option<Either<AuthFailure, Unit>> get logoutFailureOrSuccess;
@override
bool get isLoggingOut;
/// Create a copy of LogoutState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$LogoutStateImplCopyWith<_$LogoutStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -0,0 +1,6 @@
part of 'logout_bloc.dart';
@freezed
class LogoutEvent with _$LogoutEvent {
const factory LogoutEvent.logout() = _Logout;
}
@@ -0,0 +1,11 @@
part of 'logout_bloc.dart';
@freezed
class LogoutState with _$LogoutState {
factory LogoutState({
required Option<Either<AuthFailure, Unit>> logoutFailureOrSuccess,
@Default(false) bool isLoggingOut,
}) = _LogoutState;
factory LogoutState.initial() => LogoutState(logoutFailureOrSuccess: none());
}
@@ -175,7 +175,7 @@ class CategoryLoaderBloc
_searchDebounce?.cancel();
// Debounce search for better UX
_searchDebounce = Timer(Duration(milliseconds: 300), () async {
_searchDebounce = Timer(const Duration(milliseconds: 300), () async {
emit(state.copyWith(isLoadingMore: true));
_isLoadingMore = false;
@@ -237,7 +237,7 @@ class CategoryLoaderBloc
);
// After sync error, try to load local data
Timer(Duration(seconds: 2), () {
Timer(const Duration(seconds: 2), () {
add(const CategoryLoaderEvent.getCategories());
});
},
@@ -251,7 +251,7 @@ class CategoryLoaderBloc
);
// After successful sync, load the updated data
Timer(Duration(seconds: 1), () {
Timer(const Duration(seconds: 1), () {
add(const CategoryLoaderEvent.getCategories());
});
},
@@ -180,6 +180,9 @@ class CheckoutFormBloc extends Bloc<CheckoutFormEvent, CheckoutFormState> {
orderAddedItems: (e) async {
emit(state.copyWith(orderAdded: e.order));
},
customerNameChanged: (e) async {
emit(state.copyWith(customerName: e.name,));
}
);
}
}
@@ -28,6 +28,7 @@ mixin _$CheckoutFormEvent {
required TResult Function(Delivery delivery) updateDelivery,
required TResult Function(Table? table) updateTable,
required TResult Function(Order? order) orderAddedItems,
required TResult Function(String name) customerNameChanged,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
@@ -39,6 +40,7 @@ mixin _$CheckoutFormEvent {
TResult? Function(Delivery delivery)? updateDelivery,
TResult? Function(Table? table)? updateTable,
TResult? Function(Order? order)? orderAddedItems,
TResult? Function(String name)? customerNameChanged,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
@@ -50,6 +52,7 @@ mixin _$CheckoutFormEvent {
TResult Function(Delivery delivery)? updateDelivery,
TResult Function(Table? table)? updateTable,
TResult Function(Order? order)? orderAddedItems,
TResult Function(String name)? customerNameChanged,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
@@ -62,6 +65,7 @@ mixin _$CheckoutFormEvent {
required TResult Function(_UpdateDelivery value) updateDelivery,
required TResult Function(_UpdateTable value) updateTable,
required TResult Function(_OrderAddedItems value) orderAddedItems,
required TResult Function(_CustomerNameChanged value) customerNameChanged,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
@@ -73,6 +77,7 @@ mixin _$CheckoutFormEvent {
TResult? Function(_UpdateDelivery value)? updateDelivery,
TResult? Function(_UpdateTable value)? updateTable,
TResult? Function(_OrderAddedItems value)? orderAddedItems,
TResult? Function(_CustomerNameChanged value)? customerNameChanged,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
@@ -84,6 +89,7 @@ mixin _$CheckoutFormEvent {
TResult Function(_UpdateDelivery value)? updateDelivery,
TResult Function(_UpdateTable value)? updateTable,
TResult Function(_OrderAddedItems value)? orderAddedItems,
TResult Function(_CustomerNameChanged value)? customerNameChanged,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
}
@@ -195,6 +201,7 @@ class _$StartedImpl implements _Started {
required TResult Function(Delivery delivery) updateDelivery,
required TResult Function(Table? table) updateTable,
required TResult Function(Order? order) orderAddedItems,
required TResult Function(String name) customerNameChanged,
}) {
return started(items);
}
@@ -210,6 +217,7 @@ class _$StartedImpl implements _Started {
TResult? Function(Delivery delivery)? updateDelivery,
TResult? Function(Table? table)? updateTable,
TResult? Function(Order? order)? orderAddedItems,
TResult? Function(String name)? customerNameChanged,
}) {
return started?.call(items);
}
@@ -225,6 +233,7 @@ class _$StartedImpl implements _Started {
TResult Function(Delivery delivery)? updateDelivery,
TResult Function(Table? table)? updateTable,
TResult Function(Order? order)? orderAddedItems,
TResult Function(String name)? customerNameChanged,
required TResult orElse(),
}) {
if (started != null) {
@@ -244,6 +253,7 @@ class _$StartedImpl implements _Started {
required TResult Function(_UpdateDelivery value) updateDelivery,
required TResult Function(_UpdateTable value) updateTable,
required TResult Function(_OrderAddedItems value) orderAddedItems,
required TResult Function(_CustomerNameChanged value) customerNameChanged,
}) {
return started(this);
}
@@ -259,6 +269,7 @@ class _$StartedImpl implements _Started {
TResult? Function(_UpdateDelivery value)? updateDelivery,
TResult? Function(_UpdateTable value)? updateTable,
TResult? Function(_OrderAddedItems value)? orderAddedItems,
TResult? Function(_CustomerNameChanged value)? customerNameChanged,
}) {
return started?.call(this);
}
@@ -274,6 +285,7 @@ class _$StartedImpl implements _Started {
TResult Function(_UpdateDelivery value)? updateDelivery,
TResult Function(_UpdateTable value)? updateTable,
TResult Function(_OrderAddedItems value)? orderAddedItems,
TResult Function(_CustomerNameChanged value)? customerNameChanged,
required TResult orElse(),
}) {
if (started != null) {
@@ -408,6 +420,7 @@ class _$AddItemImpl implements _AddItem {
required TResult Function(Delivery delivery) updateDelivery,
required TResult Function(Table? table) updateTable,
required TResult Function(Order? order) orderAddedItems,
required TResult Function(String name) customerNameChanged,
}) {
return addItem(product, variant);
}
@@ -423,6 +436,7 @@ class _$AddItemImpl implements _AddItem {
TResult? Function(Delivery delivery)? updateDelivery,
TResult? Function(Table? table)? updateTable,
TResult? Function(Order? order)? orderAddedItems,
TResult? Function(String name)? customerNameChanged,
}) {
return addItem?.call(product, variant);
}
@@ -438,6 +452,7 @@ class _$AddItemImpl implements _AddItem {
TResult Function(Delivery delivery)? updateDelivery,
TResult Function(Table? table)? updateTable,
TResult Function(Order? order)? orderAddedItems,
TResult Function(String name)? customerNameChanged,
required TResult orElse(),
}) {
if (addItem != null) {
@@ -457,6 +472,7 @@ class _$AddItemImpl implements _AddItem {
required TResult Function(_UpdateDelivery value) updateDelivery,
required TResult Function(_UpdateTable value) updateTable,
required TResult Function(_OrderAddedItems value) orderAddedItems,
required TResult Function(_CustomerNameChanged value) customerNameChanged,
}) {
return addItem(this);
}
@@ -472,6 +488,7 @@ class _$AddItemImpl implements _AddItem {
TResult? Function(_UpdateDelivery value)? updateDelivery,
TResult? Function(_UpdateTable value)? updateTable,
TResult? Function(_OrderAddedItems value)? orderAddedItems,
TResult? Function(_CustomerNameChanged value)? customerNameChanged,
}) {
return addItem?.call(this);
}
@@ -487,6 +504,7 @@ class _$AddItemImpl implements _AddItem {
TResult Function(_UpdateDelivery value)? updateDelivery,
TResult Function(_UpdateTable value)? updateTable,
TResult Function(_OrderAddedItems value)? orderAddedItems,
TResult Function(_CustomerNameChanged value)? customerNameChanged,
required TResult orElse(),
}) {
if (addItem != null) {
@@ -623,6 +641,7 @@ class _$RemoveItemImpl implements _RemoveItem {
required TResult Function(Delivery delivery) updateDelivery,
required TResult Function(Table? table) updateTable,
required TResult Function(Order? order) orderAddedItems,
required TResult Function(String name) customerNameChanged,
}) {
return removeItem(product, variant);
}
@@ -638,6 +657,7 @@ class _$RemoveItemImpl implements _RemoveItem {
TResult? Function(Delivery delivery)? updateDelivery,
TResult? Function(Table? table)? updateTable,
TResult? Function(Order? order)? orderAddedItems,
TResult? Function(String name)? customerNameChanged,
}) {
return removeItem?.call(product, variant);
}
@@ -653,6 +673,7 @@ class _$RemoveItemImpl implements _RemoveItem {
TResult Function(Delivery delivery)? updateDelivery,
TResult Function(Table? table)? updateTable,
TResult Function(Order? order)? orderAddedItems,
TResult Function(String name)? customerNameChanged,
required TResult orElse(),
}) {
if (removeItem != null) {
@@ -672,6 +693,7 @@ class _$RemoveItemImpl implements _RemoveItem {
required TResult Function(_UpdateDelivery value) updateDelivery,
required TResult Function(_UpdateTable value) updateTable,
required TResult Function(_OrderAddedItems value) orderAddedItems,
required TResult Function(_CustomerNameChanged value) customerNameChanged,
}) {
return removeItem(this);
}
@@ -687,6 +709,7 @@ class _$RemoveItemImpl implements _RemoveItem {
TResult? Function(_UpdateDelivery value)? updateDelivery,
TResult? Function(_UpdateTable value)? updateTable,
TResult? Function(_OrderAddedItems value)? orderAddedItems,
TResult? Function(_CustomerNameChanged value)? customerNameChanged,
}) {
return removeItem?.call(this);
}
@@ -702,6 +725,7 @@ class _$RemoveItemImpl implements _RemoveItem {
TResult Function(_UpdateDelivery value)? updateDelivery,
TResult Function(_UpdateTable value)? updateTable,
TResult Function(_OrderAddedItems value)? orderAddedItems,
TResult Function(_CustomerNameChanged value)? customerNameChanged,
required TResult orElse(),
}) {
if (removeItem != null) {
@@ -828,6 +852,7 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes {
required TResult Function(Delivery delivery) updateDelivery,
required TResult Function(Table? table) updateTable,
required TResult Function(Order? order) orderAddedItems,
required TResult Function(String name) customerNameChanged,
}) {
return updateItemNotes(product, notes);
}
@@ -843,6 +868,7 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes {
TResult? Function(Delivery delivery)? updateDelivery,
TResult? Function(Table? table)? updateTable,
TResult? Function(Order? order)? orderAddedItems,
TResult? Function(String name)? customerNameChanged,
}) {
return updateItemNotes?.call(product, notes);
}
@@ -858,6 +884,7 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes {
TResult Function(Delivery delivery)? updateDelivery,
TResult Function(Table? table)? updateTable,
TResult Function(Order? order)? orderAddedItems,
TResult Function(String name)? customerNameChanged,
required TResult orElse(),
}) {
if (updateItemNotes != null) {
@@ -877,6 +904,7 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes {
required TResult Function(_UpdateDelivery value) updateDelivery,
required TResult Function(_UpdateTable value) updateTable,
required TResult Function(_OrderAddedItems value) orderAddedItems,
required TResult Function(_CustomerNameChanged value) customerNameChanged,
}) {
return updateItemNotes(this);
}
@@ -892,6 +920,7 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes {
TResult? Function(_UpdateDelivery value)? updateDelivery,
TResult? Function(_UpdateTable value)? updateTable,
TResult? Function(_OrderAddedItems value)? orderAddedItems,
TResult? Function(_CustomerNameChanged value)? customerNameChanged,
}) {
return updateItemNotes?.call(this);
}
@@ -907,6 +936,7 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes {
TResult Function(_UpdateDelivery value)? updateDelivery,
TResult Function(_UpdateTable value)? updateTable,
TResult Function(_OrderAddedItems value)? orderAddedItems,
TResult Function(_CustomerNameChanged value)? customerNameChanged,
required TResult orElse(),
}) {
if (updateItemNotes != null) {
@@ -1013,6 +1043,7 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType {
required TResult Function(Delivery delivery) updateDelivery,
required TResult Function(Table? table) updateTable,
required TResult Function(Order? order) orderAddedItems,
required TResult Function(String name) customerNameChanged,
}) {
return updateOrderType(orderType);
}
@@ -1028,6 +1059,7 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType {
TResult? Function(Delivery delivery)? updateDelivery,
TResult? Function(Table? table)? updateTable,
TResult? Function(Order? order)? orderAddedItems,
TResult? Function(String name)? customerNameChanged,
}) {
return updateOrderType?.call(orderType);
}
@@ -1043,6 +1075,7 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType {
TResult Function(Delivery delivery)? updateDelivery,
TResult Function(Table? table)? updateTable,
TResult Function(Order? order)? orderAddedItems,
TResult Function(String name)? customerNameChanged,
required TResult orElse(),
}) {
if (updateOrderType != null) {
@@ -1062,6 +1095,7 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType {
required TResult Function(_UpdateDelivery value) updateDelivery,
required TResult Function(_UpdateTable value) updateTable,
required TResult Function(_OrderAddedItems value) orderAddedItems,
required TResult Function(_CustomerNameChanged value) customerNameChanged,
}) {
return updateOrderType(this);
}
@@ -1077,6 +1111,7 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType {
TResult? Function(_UpdateDelivery value)? updateDelivery,
TResult? Function(_UpdateTable value)? updateTable,
TResult? Function(_OrderAddedItems value)? orderAddedItems,
TResult? Function(_CustomerNameChanged value)? customerNameChanged,
}) {
return updateOrderType?.call(this);
}
@@ -1092,6 +1127,7 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType {
TResult Function(_UpdateDelivery value)? updateDelivery,
TResult Function(_UpdateTable value)? updateTable,
TResult Function(_OrderAddedItems value)? orderAddedItems,
TResult Function(_CustomerNameChanged value)? customerNameChanged,
required TResult orElse(),
}) {
if (updateOrderType != null) {
@@ -1197,6 +1233,7 @@ class _$UpdateDeliveryImpl implements _UpdateDelivery {
required TResult Function(Delivery delivery) updateDelivery,
required TResult Function(Table? table) updateTable,
required TResult Function(Order? order) orderAddedItems,
required TResult Function(String name) customerNameChanged,
}) {
return updateDelivery(delivery);
}
@@ -1212,6 +1249,7 @@ class _$UpdateDeliveryImpl implements _UpdateDelivery {
TResult? Function(Delivery delivery)? updateDelivery,
TResult? Function(Table? table)? updateTable,
TResult? Function(Order? order)? orderAddedItems,
TResult? Function(String name)? customerNameChanged,
}) {
return updateDelivery?.call(delivery);
}
@@ -1227,6 +1265,7 @@ class _$UpdateDeliveryImpl implements _UpdateDelivery {
TResult Function(Delivery delivery)? updateDelivery,
TResult Function(Table? table)? updateTable,
TResult Function(Order? order)? orderAddedItems,
TResult Function(String name)? customerNameChanged,
required TResult orElse(),
}) {
if (updateDelivery != null) {
@@ -1246,6 +1285,7 @@ class _$UpdateDeliveryImpl implements _UpdateDelivery {
required TResult Function(_UpdateDelivery value) updateDelivery,
required TResult Function(_UpdateTable value) updateTable,
required TResult Function(_OrderAddedItems value) orderAddedItems,
required TResult Function(_CustomerNameChanged value) customerNameChanged,
}) {
return updateDelivery(this);
}
@@ -1261,6 +1301,7 @@ class _$UpdateDeliveryImpl implements _UpdateDelivery {
TResult? Function(_UpdateDelivery value)? updateDelivery,
TResult? Function(_UpdateTable value)? updateTable,
TResult? Function(_OrderAddedItems value)? orderAddedItems,
TResult? Function(_CustomerNameChanged value)? customerNameChanged,
}) {
return updateDelivery?.call(this);
}
@@ -1276,6 +1317,7 @@ class _$UpdateDeliveryImpl implements _UpdateDelivery {
TResult Function(_UpdateDelivery value)? updateDelivery,
TResult Function(_UpdateTable value)? updateTable,
TResult Function(_OrderAddedItems value)? orderAddedItems,
TResult Function(_CustomerNameChanged value)? customerNameChanged,
required TResult orElse(),
}) {
if (updateDelivery != null) {
@@ -1392,6 +1434,7 @@ class _$UpdateTableImpl implements _UpdateTable {
required TResult Function(Delivery delivery) updateDelivery,
required TResult Function(Table? table) updateTable,
required TResult Function(Order? order) orderAddedItems,
required TResult Function(String name) customerNameChanged,
}) {
return updateTable(table);
}
@@ -1407,6 +1450,7 @@ class _$UpdateTableImpl implements _UpdateTable {
TResult? Function(Delivery delivery)? updateDelivery,
TResult? Function(Table? table)? updateTable,
TResult? Function(Order? order)? orderAddedItems,
TResult? Function(String name)? customerNameChanged,
}) {
return updateTable?.call(table);
}
@@ -1422,6 +1466,7 @@ class _$UpdateTableImpl implements _UpdateTable {
TResult Function(Delivery delivery)? updateDelivery,
TResult Function(Table? table)? updateTable,
TResult Function(Order? order)? orderAddedItems,
TResult Function(String name)? customerNameChanged,
required TResult orElse(),
}) {
if (updateTable != null) {
@@ -1441,6 +1486,7 @@ class _$UpdateTableImpl implements _UpdateTable {
required TResult Function(_UpdateDelivery value) updateDelivery,
required TResult Function(_UpdateTable value) updateTable,
required TResult Function(_OrderAddedItems value) orderAddedItems,
required TResult Function(_CustomerNameChanged value) customerNameChanged,
}) {
return updateTable(this);
}
@@ -1456,6 +1502,7 @@ class _$UpdateTableImpl implements _UpdateTable {
TResult? Function(_UpdateDelivery value)? updateDelivery,
TResult? Function(_UpdateTable value)? updateTable,
TResult? Function(_OrderAddedItems value)? orderAddedItems,
TResult? Function(_CustomerNameChanged value)? customerNameChanged,
}) {
return updateTable?.call(this);
}
@@ -1471,6 +1518,7 @@ class _$UpdateTableImpl implements _UpdateTable {
TResult Function(_UpdateDelivery value)? updateDelivery,
TResult Function(_UpdateTable value)? updateTable,
TResult Function(_OrderAddedItems value)? orderAddedItems,
TResult Function(_CustomerNameChanged value)? customerNameChanged,
required TResult orElse(),
}) {
if (updateTable != null) {
@@ -1590,6 +1638,7 @@ class _$OrderAddedItemsImpl implements _OrderAddedItems {
required TResult Function(Delivery delivery) updateDelivery,
required TResult Function(Table? table) updateTable,
required TResult Function(Order? order) orderAddedItems,
required TResult Function(String name) customerNameChanged,
}) {
return orderAddedItems(order);
}
@@ -1605,6 +1654,7 @@ class _$OrderAddedItemsImpl implements _OrderAddedItems {
TResult? Function(Delivery delivery)? updateDelivery,
TResult? Function(Table? table)? updateTable,
TResult? Function(Order? order)? orderAddedItems,
TResult? Function(String name)? customerNameChanged,
}) {
return orderAddedItems?.call(order);
}
@@ -1620,6 +1670,7 @@ class _$OrderAddedItemsImpl implements _OrderAddedItems {
TResult Function(Delivery delivery)? updateDelivery,
TResult Function(Table? table)? updateTable,
TResult Function(Order? order)? orderAddedItems,
TResult Function(String name)? customerNameChanged,
required TResult orElse(),
}) {
if (orderAddedItems != null) {
@@ -1639,6 +1690,7 @@ class _$OrderAddedItemsImpl implements _OrderAddedItems {
required TResult Function(_UpdateDelivery value) updateDelivery,
required TResult Function(_UpdateTable value) updateTable,
required TResult Function(_OrderAddedItems value) orderAddedItems,
required TResult Function(_CustomerNameChanged value) customerNameChanged,
}) {
return orderAddedItems(this);
}
@@ -1654,6 +1706,7 @@ class _$OrderAddedItemsImpl implements _OrderAddedItems {
TResult? Function(_UpdateDelivery value)? updateDelivery,
TResult? Function(_UpdateTable value)? updateTable,
TResult? Function(_OrderAddedItems value)? orderAddedItems,
TResult? Function(_CustomerNameChanged value)? customerNameChanged,
}) {
return orderAddedItems?.call(this);
}
@@ -1669,6 +1722,7 @@ class _$OrderAddedItemsImpl implements _OrderAddedItems {
TResult Function(_UpdateDelivery value)? updateDelivery,
TResult Function(_UpdateTable value)? updateTable,
TResult Function(_OrderAddedItems value)? orderAddedItems,
TResult Function(_CustomerNameChanged value)? customerNameChanged,
required TResult orElse(),
}) {
if (orderAddedItems != null) {
@@ -1690,6 +1744,195 @@ abstract class _OrderAddedItems implements CheckoutFormEvent {
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$CustomerNameChangedImplCopyWith<$Res> {
factory _$$CustomerNameChangedImplCopyWith(
_$CustomerNameChangedImpl value,
$Res Function(_$CustomerNameChangedImpl) then,
) = __$$CustomerNameChangedImplCopyWithImpl<$Res>;
@useResult
$Res call({String name});
}
/// @nodoc
class __$$CustomerNameChangedImplCopyWithImpl<$Res>
extends _$CheckoutFormEventCopyWithImpl<$Res, _$CustomerNameChangedImpl>
implements _$$CustomerNameChangedImplCopyWith<$Res> {
__$$CustomerNameChangedImplCopyWithImpl(
_$CustomerNameChangedImpl _value,
$Res Function(_$CustomerNameChangedImpl) _then,
) : super(_value, _then);
/// Create a copy of CheckoutFormEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({Object? name = null}) {
return _then(
_$CustomerNameChangedImpl(
null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
),
);
}
}
/// @nodoc
class _$CustomerNameChangedImpl implements _CustomerNameChanged {
const _$CustomerNameChangedImpl(this.name);
@override
final String name;
@override
String toString() {
return 'CheckoutFormEvent.customerNameChanged(name: $name)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$CustomerNameChangedImpl &&
(identical(other.name, name) || other.name == name));
}
@override
int get hashCode => Object.hash(runtimeType, name);
/// Create a copy of CheckoutFormEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$CustomerNameChangedImplCopyWith<_$CustomerNameChangedImpl> get copyWith =>
__$$CustomerNameChangedImplCopyWithImpl<_$CustomerNameChangedImpl>(
this,
_$identity,
);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(List<ProductQuantity> items) started,
required TResult Function(Product product, ProductVariant? variant) addItem,
required TResult Function(Product product, ProductVariant? variant)
removeItem,
required TResult Function(Product product, String notes) updateItemNotes,
required TResult Function(OrderType orderType) updateOrderType,
required TResult Function(Delivery delivery) updateDelivery,
required TResult Function(Table? table) updateTable,
required TResult Function(Order? order) orderAddedItems,
required TResult Function(String name) customerNameChanged,
}) {
return customerNameChanged(name);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(List<ProductQuantity> items)? started,
TResult? Function(Product product, ProductVariant? variant)? addItem,
TResult? Function(Product product, ProductVariant? variant)? removeItem,
TResult? Function(Product product, String notes)? updateItemNotes,
TResult? Function(OrderType orderType)? updateOrderType,
TResult? Function(Delivery delivery)? updateDelivery,
TResult? Function(Table? table)? updateTable,
TResult? Function(Order? order)? orderAddedItems,
TResult? Function(String name)? customerNameChanged,
}) {
return customerNameChanged?.call(name);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(List<ProductQuantity> items)? started,
TResult Function(Product product, ProductVariant? variant)? addItem,
TResult Function(Product product, ProductVariant? variant)? removeItem,
TResult Function(Product product, String notes)? updateItemNotes,
TResult Function(OrderType orderType)? updateOrderType,
TResult Function(Delivery delivery)? updateDelivery,
TResult Function(Table? table)? updateTable,
TResult Function(Order? order)? orderAddedItems,
TResult Function(String name)? customerNameChanged,
required TResult orElse(),
}) {
if (customerNameChanged != null) {
return customerNameChanged(name);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Started value) started,
required TResult Function(_AddItem value) addItem,
required TResult Function(_RemoveItem value) removeItem,
required TResult Function(_UpdateItemNotes value) updateItemNotes,
required TResult Function(_UpdateOrderType value) updateOrderType,
required TResult Function(_UpdateDelivery value) updateDelivery,
required TResult Function(_UpdateTable value) updateTable,
required TResult Function(_OrderAddedItems value) orderAddedItems,
required TResult Function(_CustomerNameChanged value) customerNameChanged,
}) {
return customerNameChanged(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Started value)? started,
TResult? Function(_AddItem value)? addItem,
TResult? Function(_RemoveItem value)? removeItem,
TResult? Function(_UpdateItemNotes value)? updateItemNotes,
TResult? Function(_UpdateOrderType value)? updateOrderType,
TResult? Function(_UpdateDelivery value)? updateDelivery,
TResult? Function(_UpdateTable value)? updateTable,
TResult? Function(_OrderAddedItems value)? orderAddedItems,
TResult? Function(_CustomerNameChanged value)? customerNameChanged,
}) {
return customerNameChanged?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Started value)? started,
TResult Function(_AddItem value)? addItem,
TResult Function(_RemoveItem value)? removeItem,
TResult Function(_UpdateItemNotes value)? updateItemNotes,
TResult Function(_UpdateOrderType value)? updateOrderType,
TResult Function(_UpdateDelivery value)? updateDelivery,
TResult Function(_UpdateTable value)? updateTable,
TResult Function(_OrderAddedItems value)? orderAddedItems,
TResult Function(_CustomerNameChanged value)? customerNameChanged,
required TResult orElse(),
}) {
if (customerNameChanged != null) {
return customerNameChanged(this);
}
return orElse();
}
}
abstract class _CustomerNameChanged implements CheckoutFormEvent {
const factory _CustomerNameChanged(final String name) =
_$CustomerNameChangedImpl;
String get name;
/// Create a copy of CheckoutFormEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$CustomerNameChangedImplCopyWith<_$CustomerNameChangedImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$CheckoutFormState {
Order? get orderAdded => throw _privateConstructorUsedError;
@@ -1702,6 +1945,7 @@ mixin _$CheckoutFormState {
int get totalPrice => throw _privateConstructorUsedError;
String get draftName => throw _privateConstructorUsedError;
OrderType get orderType => throw _privateConstructorUsedError;
String get customerName => throw _privateConstructorUsedError;
Delivery? get delivery => throw _privateConstructorUsedError;
Table? get table => throw _privateConstructorUsedError;
bool get isLoading => throw _privateConstructorUsedError;
@@ -1731,6 +1975,7 @@ abstract class $CheckoutFormStateCopyWith<$Res> {
int totalPrice,
String draftName,
OrderType orderType,
String customerName,
Delivery? delivery,
Table? table,
bool isLoading,
@@ -1765,6 +2010,7 @@ class _$CheckoutFormStateCopyWithImpl<$Res, $Val extends CheckoutFormState>
Object? totalPrice = null,
Object? draftName = null,
Object? orderType = null,
Object? customerName = null,
Object? delivery = freezed,
Object? table = freezed,
Object? isLoading = null,
@@ -1811,6 +2057,10 @@ class _$CheckoutFormStateCopyWithImpl<$Res, $Val extends CheckoutFormState>
? _value.orderType
: orderType // ignore: cast_nullable_to_non_nullable
as OrderType,
customerName: null == customerName
? _value.customerName
: customerName // ignore: cast_nullable_to_non_nullable
as String,
delivery: freezed == delivery
? _value.delivery
: delivery // ignore: cast_nullable_to_non_nullable
@@ -1877,6 +2127,7 @@ abstract class _$$CheckoutFormStateImplCopyWith<$Res>
int totalPrice,
String draftName,
OrderType orderType,
String customerName,
Delivery? delivery,
Table? table,
bool isLoading,
@@ -1912,6 +2163,7 @@ class __$$CheckoutFormStateImplCopyWithImpl<$Res>
Object? totalPrice = null,
Object? draftName = null,
Object? orderType = null,
Object? customerName = null,
Object? delivery = freezed,
Object? table = freezed,
Object? isLoading = null,
@@ -1958,6 +2210,10 @@ class __$$CheckoutFormStateImplCopyWithImpl<$Res>
? _value.orderType
: orderType // ignore: cast_nullable_to_non_nullable
as OrderType,
customerName: null == customerName
? _value.customerName
: customerName // ignore: cast_nullable_to_non_nullable
as String,
delivery: freezed == delivery
? _value.delivery
: delivery // ignore: cast_nullable_to_non_nullable
@@ -1989,6 +2245,7 @@ class _$CheckoutFormStateImpl implements _CheckoutFormState {
required this.totalPrice,
required this.draftName,
required this.orderType,
required this.customerName,
this.delivery,
this.table,
this.isLoading = false,
@@ -2021,6 +2278,8 @@ class _$CheckoutFormStateImpl implements _CheckoutFormState {
@override
final OrderType orderType;
@override
final String customerName;
@override
final Delivery? delivery;
@override
final Table? table;
@@ -2030,7 +2289,7 @@ class _$CheckoutFormStateImpl implements _CheckoutFormState {
@override
String toString() {
return 'CheckoutFormState(orderAdded: $orderAdded, items: $items, discount: $discount, discountAmount: $discountAmount, tax: $tax, serviceCharge: $serviceCharge, totalQuantity: $totalQuantity, totalPrice: $totalPrice, draftName: $draftName, orderType: $orderType, delivery: $delivery, table: $table, isLoading: $isLoading)';
return 'CheckoutFormState(orderAdded: $orderAdded, items: $items, discount: $discount, discountAmount: $discountAmount, tax: $tax, serviceCharge: $serviceCharge, totalQuantity: $totalQuantity, totalPrice: $totalPrice, draftName: $draftName, orderType: $orderType, customerName: $customerName, delivery: $delivery, table: $table, isLoading: $isLoading)';
}
@override
@@ -2056,6 +2315,8 @@ class _$CheckoutFormStateImpl implements _CheckoutFormState {
other.draftName == draftName) &&
(identical(other.orderType, orderType) ||
other.orderType == orderType) &&
(identical(other.customerName, customerName) ||
other.customerName == customerName) &&
(identical(other.delivery, delivery) ||
other.delivery == delivery) &&
(identical(other.table, table) || other.table == table) &&
@@ -2076,6 +2337,7 @@ class _$CheckoutFormStateImpl implements _CheckoutFormState {
totalPrice,
draftName,
orderType,
customerName,
delivery,
table,
isLoading,
@@ -2105,6 +2367,7 @@ abstract class _CheckoutFormState implements CheckoutFormState {
required final int totalPrice,
required final String draftName,
required final OrderType orderType,
required final String customerName,
final Delivery? delivery,
final Table? table,
final bool isLoading,
@@ -2131,6 +2394,8 @@ abstract class _CheckoutFormState implements CheckoutFormState {
@override
OrderType get orderType;
@override
String get customerName;
@override
Delivery? get delivery;
@override
Table? get table;
@@ -30,4 +30,6 @@ class CheckoutFormEvent with _$CheckoutFormEvent {
const factory CheckoutFormEvent.orderAddedItems(Order? order) =
_OrderAddedItems;
const factory CheckoutFormEvent.customerNameChanged(String name) =
_CustomerNameChanged;
}
@@ -13,6 +13,7 @@ class CheckoutFormState with _$CheckoutFormState {
required int totalPrice,
required String draftName,
required OrderType orderType,
required String customerName,
Delivery? delivery,
Table? table,
@Default(false) bool isLoading,
@@ -27,6 +28,7 @@ class CheckoutFormState with _$CheckoutFormState {
totalQuantity: 0,
totalPrice: 0,
draftName: '',
customerName: '',
orderType: OrderType.dineIn,
);
}
@@ -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(
@@ -39,12 +39,108 @@ class PrintStruckBloc extends Bloc<PrintStruckEvent, PrintStruckState> {
),
);
},
saveOrder: (e) async {
cashier: (e) async {
Either<PrinterFailure, Unit> failureOrSuccess;
emit(state.copyWith(isPrinting: true, failureOrPrintStruck: none()));
failureOrSuccess = await _printerRepository.printStruckSaveOrder(
failureOrSuccess = await _printerRepository.printStruckCashier(
order: e.order,
);
emit(
state.copyWith(
isPrinting: false,
failureOrPrintStruck: optionOf(failureOrSuccess),
),
);
},
payment: (e) async {
Either<PrinterFailure, Unit> failureOrSuccess;
emit(state.copyWith(isPrinting: true, failureOrPrintStruck: none()));
failureOrSuccess = await _printerRepository.printStruckPayment(
order: e.order,
);
emit(
state.copyWith(
isPrinting: false,
failureOrPrintStruck: optionOf(failureOrSuccess),
),
);
},
voided: (e) async {
Either<PrinterFailure, Unit> failureOrSuccess;
emit(state.copyWith(isPrinting: true, failureOrPrintStruck: none()));
failureOrSuccess = await _printerRepository.printStruckVoid(
order: e.order,
);
emit(
state.copyWith(
isPrinting: false,
failureOrPrintStruck: optionOf(failureOrSuccess),
),
);
},
splitBill: (e) async {
Either<PrinterFailure, Unit> failureOrSuccess;
emit(state.copyWith(isPrinting: true, failureOrPrintStruck: none()));
failureOrSuccess = await _printerRepository.printStruckSplitBill(
order: e.order,
);
emit(
state.copyWith(
isPrinting: false,
failureOrPrintStruck: optionOf(failureOrSuccess),
),
);
},
kitchen: (e) async {
Either<PrinterFailure, Unit> failureOrSuccess;
emit(state.copyWith(isPrinting: true, failureOrPrintStruck: none()));
failureOrSuccess = await _printerRepository.printStruckKitchen(
order: e.order,
);
emit(
state.copyWith(
isPrinting: false,
failureOrPrintStruck: optionOf(failureOrSuccess),
),
);
},
bar: (e) async {
Either<PrinterFailure, Unit> failureOrSuccess;
emit(state.copyWith(isPrinting: true, failureOrPrintStruck: none()));
failureOrSuccess = await _printerRepository.printStruckBar(
order: e.order,
);
emit(
state.copyWith(
isPrinting: false,
failureOrPrintStruck: optionOf(failureOrSuccess),
),
);
},
checker: (e) async {
Either<PrinterFailure, Unit> failureOrSuccess;
emit(state.copyWith(isPrinting: true, failureOrPrintStruck: none()));
failureOrSuccess = await _printerRepository.printStruckChecker(
order: e.order,
);
File diff suppressed because it is too large Load Diff
@@ -3,5 +3,11 @@ part of 'print_struck_bloc.dart';
@freezed
class PrintStruckEvent with _$PrintStruckEvent {
const factory PrintStruckEvent.order(Order order) = _Order;
const factory PrintStruckEvent.saveOrder(Order order) = _SaveOrder;
const factory PrintStruckEvent.cashier(Order order) = _Cashier;
const factory PrintStruckEvent.payment(Order order) = _Payment;
const factory PrintStruckEvent.voided(Order order) = _Voided;
const factory PrintStruckEvent.checker(Order order) = _Checker;
const factory PrintStruckEvent.kitchen(Order order) = _Kitchen;
const factory PrintStruckEvent.bar(Order order) = _Bar;
const factory PrintStruckEvent.splitBill(Order order) = _SplitBill;
}
@@ -117,6 +117,13 @@ class PrinterFormBloc extends Bloc<PrinterFormEvent, PrinterFormState> {
final currentOutlet = await _outletRepository.currentOutlet();
// Ambil paper size dari DB, fallback ke state.paper jika belum tersimpan
final savedPrinter = await _printerRepository.getPrinterByCode(e.code);
final paper = savedPrinter.fold(
(_) => int.tryParse(state.paper) ?? 58,
(p) => int.tryParse(p.paper) ?? 58,
);
List<int> printValue = [];
if (e.code == "receipt") {
@@ -124,29 +131,34 @@ class PrinterFormBloc extends Bloc<PrinterFormEvent, PrinterFormState> {
order: Order.mockOrder(),
outlet: currentOutlet,
cashierName: 'Kasir Test',
paper: paper,
);
} else if (e.code == "checker") {
printValue = await PrintUi().printChecker(
order: Order.mockOrder(),
outlet: currentOutlet,
cashierName: 'Kasir Test',
paper: paper,
);
} else if (e.code == 'kitchen') {
printValue = await PrintUi().printKitchen(
order: Order.mockOrder(),
outlet: currentOutlet,
cashierName: 'Kasir Test',
paper: paper,
);
} else if (e.code == 'bar') {
printValue = await PrintUi().printBar(
order: Order.mockOrder(),
outlet: currentOutlet,
cashierName: 'Kasir Test',
paper: paper,
);
} else if (e.code == 'ticket') {
printValue = await PrintUi().printTicket(
order: Order.mockOrder(),
outlet: currentOutlet,
paper: paper,
);
}
@@ -154,7 +166,7 @@ class PrinterFormBloc extends Bloc<PrinterFormEvent, PrinterFormState> {
code: e.code,
name: state.name,
address: e.macAccdress,
paper: state.paper,
paper: paper.toString(),
type: state.type,
);
@@ -43,7 +43,7 @@ class ProductLoaderBloc extends Bloc<ProductLoaderEvent, ProductLoaderState> {
state.copyWith(
isLoadingMore: false,
failureOptionProduct: optionOf(
ProductFailure.dynamicErrorMessage(
const ProductFailure.dynamicErrorMessage(
'Database lokal belum siap. Silakan lakukan sinkronisasi data terlebih dahulu.',
),
),
@@ -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(
@@ -0,0 +1,234 @@
import 'package:bloc/bloc.dart';
import 'package:dartz/dartz.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart';
import '../../../domain/category/category.dart';
import '../../../domain/product/product.dart';
part 'sync_setting_event.dart';
part 'sync_setting_state.dart';
part 'sync_setting_bloc.freezed.dart';
@injectable
class SyncSettingBloc extends Bloc<SyncSettingEvent, SyncSettingState> {
final IProductRepository _productRepository;
final ICategoryRepository _categoryRepository;
SyncSettingBloc(this._productRepository, this._categoryRepository)
: super(SyncSettingState.initial()) {
on<SyncSettingEvent>(_onSyncSettingEvent);
}
Future<void> _onSyncSettingEvent(
SyncSettingEvent event,
Emitter<SyncSettingState> emit,
) {
return event.map(
loadStats: (e) => _onLoadStats(emit),
syncAllData: (e) => _onSyncAllData(emit),
clearAllData: (e) => _onClearAllData(emit),
syncProducts: (e) => _onSyncProducts(emit),
syncCategories: (e) => _onSyncCategories(emit),
clearCategories: (e) => _onClearCategories(emit),
clearProducts: (e) => _onClearProducts(emit),
);
}
Future<void> _onLoadStats(Emitter<SyncSettingState> emit) async {
emit(
state.copyWith(
isLoading: true,
failureOptionProduct: none(),
failureOptionCategory: none(),
),
);
final productStats = await _productRepository.getDatabaseStats();
final categoryStats = await _categoryRepository.getDatabaseStats();
productStats.fold(
(failure) {
emit(
state.copyWith(
isLoading: false,
failureOptionProduct: optionOf(failure),
),
);
},
(data) {
emit(
state.copyWith(
isLoading: false,
productStats: data,
failureOptionProduct: none(),
),
);
},
);
categoryStats.fold(
(failure) {
emit(
state.copyWith(
isLoading: false,
failureOptionCategory: optionOf(failure),
),
);
},
(data) {
emit(
state.copyWith(
isLoading: false,
categoryStats: data,
failureOptionCategory: none(),
),
);
},
);
}
Future<void> _onSyncAllData(Emitter<SyncSettingState> emit) async {
emit(
state.copyWith(
isSyncing: true,
failureOptionSyncCategory: none(),
failureOptionSyncProduct: none(),
),
);
// Sync categories first
final categoryResult = await _categoryRepository.syncAllCategories();
bool categorySuccess = false;
categoryResult.fold(
(failure) {
emit(
state.copyWith(failureOptionSyncCategory: optionOf(left(failure))),
);
},
(success) {
categorySuccess = true;
emit(
state.copyWith(failureOptionSyncCategory: optionOf(right(success))),
);
},
);
// Only sync products if categories synced successfully
if (categorySuccess) {
final productResult = await _productRepository.syncAllProducts();
productResult.fold(
(failure) {
emit(
state.copyWith(
isSyncing: false,
failureOptionSyncProduct: optionOf(left(failure)),
),
);
},
(success) {
emit(
state.copyWith(
isSyncing: false,
failureOptionSyncProduct: optionOf(right(success)),
),
);
},
);
} else {
emit(state.copyWith(isSyncing: false));
}
// Reload stats after sync
add(const SyncSettingEvent.loadStats());
}
Future<void> _onClearAllData(Emitter<SyncSettingState> emit) async {
emit(state.copyWith(isLoading: true));
try {
// Clear products and categories
await _productRepository.clearAllProducts();
await _categoryRepository.clearAllCategories();
// Clear caches
_productRepository.clearCache();
_categoryRepository.clearCache();
emit(state.copyWith(isLoading: false));
// Reload stats after clearing
add(const SyncSettingEvent.loadStats());
} catch (e) {
emit(state.copyWith(isLoading: false));
}
}
Future<void> _onSyncProducts(Emitter<SyncSettingState> emit) async {
emit(state.copyWith(isLoading: true, failureOptionSyncProduct: none()));
final result = await _productRepository.syncAllProducts();
result.fold(
(failure) {
emit(
state.copyWith(
isLoading: false,
failureOptionSyncProduct: optionOf(left(failure)),
),
);
},
(success) {
emit(
state.copyWith(
isLoading: false,
failureOptionSyncProduct: optionOf(right(success)),
),
);
},
);
// Reload stats after sync
add(const SyncSettingEvent.loadStats());
}
Future<void> _onSyncCategories(Emitter<SyncSettingState> emit) async {
emit(state.copyWith(isLoading: true, failureOptionSyncCategory: none()));
final result = await _categoryRepository.syncAllCategories();
result.fold(
(failure) {
emit(
state.copyWith(
isLoading: false,
failureOptionSyncCategory: optionOf(left(failure)),
),
);
},
(success) {
emit(
state.copyWith(
isLoading: false,
failureOptionSyncCategory: optionOf(right(success)),
),
);
},
);
// Reload stats after sync
add(const SyncSettingEvent.loadStats());
}
Future<void> _onClearCategories(Emitter<SyncSettingState> emit) async {
await _categoryRepository.clearAllCategories();
_categoryRepository.clearCache();
}
Future<void> _onClearProducts(Emitter<SyncSettingState> emit) async {
await _productRepository.clearAllProducts();
_productRepository.clearCache();
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,12 @@
part of 'sync_setting_bloc.dart';
@freezed
class SyncSettingEvent with _$SyncSettingEvent {
const factory SyncSettingEvent.loadStats() = _LoadStats;
const factory SyncSettingEvent.syncAllData() = _SyncAllData;
const factory SyncSettingEvent.clearAllData() = _ClearAllData;
const factory SyncSettingEvent.syncProducts() = _SyncProducts;
const factory SyncSettingEvent.syncCategories() = _SyncCategories;
const factory SyncSettingEvent.clearCategories() = _ClearCategories;
const factory SyncSettingEvent.clearProducts() = _ClearProducts;
}
@@ -0,0 +1,24 @@
part of 'sync_setting_bloc.dart';
@freezed
class SyncSettingState with _$SyncSettingState {
factory SyncSettingState({
required Map<String, dynamic> productStats,
required Map<String, dynamic> categoryStats,
required Option<CategoryFailure> failureOptionCategory,
required Option<ProductFailure> failureOptionProduct,
required Option<Either<ProductFailure, String>> failureOptionSyncProduct,
required Option<Either<CategoryFailure, String>> failureOptionSyncCategory,
@Default(false) bool isSyncing,
@Default(false) bool isLoading,
}) = _SyncSettingState;
factory SyncSettingState.initial() => SyncSettingState(
productStats: {},
categoryStats: {},
failureOptionCategory: none(),
failureOptionProduct: none(),
failureOptionSyncProduct: none(),
failureOptionSyncCategory: none(),
);
}
@@ -1,7 +1,9 @@
import 'package:bloc/bloc.dart';
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';
@@ -49,9 +51,10 @@ class VoidFormBloc extends Bloc<VoidFormEvent, VoidFormState> {
// Recalculate total price
int newTotal = 0;
for (var entry in newQuantities.entries) {
final item = state.pendingItems.firstWhere(
final item = state.pendingItems.firstWhereOrNull(
(item) => item.id == entry.key,
);
if (item == null) continue;
newTotal += item.unitPrice.toInt() * entry.value;
}
@@ -71,16 +74,20 @@ class VoidFormBloc extends Bloc<VoidFormEvent, VoidFormState> {
if (state.voidType.isItem) {
state.selectedItemQuantities.forEach((itemId, voidQty) {
final originalItem = state.order.orderItems.firstWhere(
final originalItem = state.order.orderItems.firstWhereOrNull(
(item) => item.id == itemId,
);
if (originalItem == null) return;
voidItems.add(
OrderItem(
id: originalItem.id,
orderId: originalItem.orderId,
productId: originalItem.productId,
productName: originalItem.productName,
categoryId: originalItem.categoryId,
categoryName: originalItem.status,
productVariantId: originalItem.productVariantId,
productVariantName: originalItem.productVariantName,
quantity: voidQty,
@@ -93,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(
@@ -112,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());
@@ -4,7 +4,6 @@ import 'package:firebase_crashlytics/firebase_crashlytics.dart';
class CrashlyticsInterceptor extends Interceptor {
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
// Log semua error ke Crashlytics
FirebaseCrashlytics.instance.recordError(
err,
err.stackTrace,
@@ -46,6 +45,32 @@ class CrashlyticsInterceptor extends Interceptor {
'Status Code: ${err.response?.statusCode ?? 'N/A'}',
'Error Type: ${err.type.name}',
'Message: ${err.message ?? 'No message'}',
// Tambahan
'Request Data: ${_sanitizeData(err.requestOptions.data)}',
'Response Body: ${err.response?.data ?? 'No response'}',
'Headers: ${err.requestOptions.headers.entries.where((e) => e.key.toLowerCase() != 'authorization').toList()}',
];
}
/// Hindari log data sensitif
dynamic _sanitizeData(dynamic data) {
if (data == null) return 'No data';
if (data is Map) {
final sanitized = Map.from(data);
const sensitiveKeys = [
'password',
'token',
'secret',
'pin',
'card_number',
];
for (final key in sensitiveKeys) {
if (sanitized.containsKey(key)) {
sanitized[key] = '***';
}
}
return sanitized;
}
return data;
}
}
@@ -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;
}
}
+4 -1
View File
@@ -1,5 +1,8 @@
import '../../env.dart';
import '../../injection.dart';
class AppConstant {
static const String appName = "Apskel POS";
static const String dbName = "apskel_pos.db";
static String dbName = getIt<Env>().dbName;
static const int cacheExpire = 10; // in minutes
}
+4 -2
View File
@@ -17,7 +17,7 @@ class DatabaseHelper {
return await openDatabase(
path,
version: 2, // 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,
@@ -68,6 +69,7 @@ class DatabaseHelper {
name TEXT NOT NULL,
description TEXT,
business_type TEXT,
sort_order INTEGER DEFAULT 0,
metadata TEXT,
is_active INTEGER DEFAULT 1,
created_at TEXT,
@@ -109,7 +111,7 @@ class DatabaseHelper {
Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
if (oldVersion < 2) {
await db.execute(
'ALTER TABLE categories ADD COLUMN "order" INTEGER DEFAULT 0',
'ALTER TABLE products ADD COLUMN print_to_checker INTEGER DEFAULT 0',
);
}
}
+21
View File
@@ -0,0 +1,21 @@
import 'package:device_info_plus/device_info_plus.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:injectable/injectable.dart';
import 'package:package_info_plus/package_info_plus.dart';
@module
abstract class FirebaseDi {
@lazySingleton
FirebaseMessaging get messaging => FirebaseMessaging.instance;
@lazySingleton
FlutterLocalNotificationsPlugin get localNotifications =>
FlutterLocalNotificationsPlugin();
@lazySingleton
DeviceInfoPlugin get deviceInfo => DeviceInfoPlugin();
@preResolve
Future<PackageInfo> get packageInfo => PackageInfo.fromPlatform();
}
@@ -0,0 +1,64 @@
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:injectable/injectable.dart';
import 'package:package_info_plus/package_info_plus.dart';
@lazySingleton
class DeviceInfoService {
DeviceInfoService(this._deviceInfo, this._packageInfo);
final DeviceInfoPlugin _deviceInfo;
final PackageInfo _packageInfo;
String get appVersion => '${_packageInfo.version}+${_packageInfo.buildNumber}';
Future<Map<String, String>> getDevicePayload() async {
final info = await _getDeviceInfo();
return {
'device_id': info['device_id'] ?? '',
'device_name': info['device_name'] ?? '',
'device_type': info['device_type'] ?? 'mobile',
'platform': _getPlatform(),
'app_version': appVersion,
'os_version': info['os_version'] ?? '',
};
}
String _getPlatform() {
if (Platform.isAndroid) return 'android';
if (Platform.isIOS) return 'ios';
return 'web';
}
Future<Map<String, String>> _getDeviceInfo() async {
if (Platform.isAndroid) {
final android = await _deviceInfo.androidInfo;
return {
'device_id': android.id,
'device_name': '${android.manufacturer} ${android.model}',
'device_type': _resolveDeviceType(android.model),
'os_version': 'Android ${android.version.release} (SDK ${android.version.sdkInt})',
};
} else if (Platform.isIOS) {
final ios = await _deviceInfo.iosInfo;
return {
'device_id': ios.identifierForVendor ?? '',
'device_name': ios.name,
'device_type': _resolveDeviceType(ios.model),
'os_version': '${ios.systemName} ${ios.systemVersion}',
};
}
return {'device_type': 'desktop'};
}
/// Deteksi device_type berdasarkan nama model
/// Nilai valid: mobile | tablet | desktop
String _resolveDeviceType(String model) {
final lower = model.toLowerCase();
if (lower.contains('tablet') || lower.contains('tab') || lower.contains('ipad')) {
return 'tablet';
}
return 'mobile';
}
}
+196
View File
@@ -0,0 +1,196 @@
import 'dart:developer';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:injectable/injectable.dart';
/// Background message handler — harus top-level function
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
log('[FCM] Background message: ${message.messageId}');
}
@lazySingleton
class FcmService {
FcmService(this._messaging, this._localNotifications);
final FirebaseMessaging _messaging;
final FlutterLocalNotificationsPlugin _localNotifications;
/// Inisialisasi FCM: minta permission, set handler, ambil token
Future<void> initialize() async {
// Setup local notifications
await _setupLocalNotifications();
// Register background handler
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
// Minta permission notifikasi (iOS & Android 13+)
final settings = await _messaging.requestPermission(
alert: true,
badge: true,
sound: true,
provisional: false,
);
log('[FCM] Permission status: ${settings.authorizationStatus}');
if (settings.authorizationStatus == AuthorizationStatus.authorized ||
settings.authorizationStatus == AuthorizationStatus.provisional) {
await _setupTokenHandling();
_setupForegroundHandler();
_setupOpenedAppHandler();
}
}
Future<void> _setupLocalNotifications() async {
const androidSettings = AndroidInitializationSettings('@drawable/ic_notification');
const iosSettings = DarwinInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
requestSoundPermission: true,
);
const initSettings = InitializationSettings(
android: androidSettings,
iOS: iosSettings,
);
await _localNotifications.initialize(
initSettings,
onDidReceiveNotificationResponse: _onNotificationTapped,
);
// Android notification channel
const channel = AndroidNotificationChannel(
'high_importance_channel',
'High Importance Notifications',
description: 'This channel is used for important notifications.',
importance: Importance.high,
playSound: true,
);
await _localNotifications
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
}
void _onNotificationTapped(NotificationResponse response) {
log('[FCM] Notification tapped: ${response.payload}');
// TODO: handle navigation berdasarkan payload
}
Future<void> _setupTokenHandling() async {
final token = await getToken();
log('========================================');
log('[FCM] TOKEN: $token');
log('========================================');
// TODO: kirim token ke server jika diperlukan
_messaging.onTokenRefresh.listen((newToken) {
log('========================================');
log('[FCM] TOKEN REFRESHED: $newToken');
log('========================================');
// TODO: kirim token baru ke server jika diperlukan
});
}
void _setupForegroundHandler() {
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
log('[FCM] Foreground message: ${message.messageId}');
log('[FCM] Title: ${message.notification?.title}');
log('[FCM] Body: ${message.notification?.body}');
log('[FCM] Data: ${message.data}');
// Tampilkan notifikasi lokal saat app di foreground
final notification = message.notification;
if (notification != null) {
_showLocalNotification(
id: message.hashCode,
title: notification.title ?? '',
body: notification.body ?? '',
payload: message.data.toString(),
);
}
});
}
void _setupOpenedAppHandler() {
// App dibuka dari notifikasi saat terminated
_messaging.getInitialMessage().then((RemoteMessage? message) {
if (message != null) {
log('[FCM] App opened from terminated via: ${message.messageId}');
_handleMessageNavigation(message);
}
});
// App dibuka dari notifikasi saat background
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
log('[FCM] App opened from background via: ${message.messageId}');
_handleMessageNavigation(message);
});
}
void _handleMessageNavigation(RemoteMessage message) {
// TODO: tambahkan logika navigasi berdasarkan message.data
log('[FCM] Handle navigation for: ${message.data}');
}
Future<void> _showLocalNotification({
required int id,
required String title,
required String body,
String? payload,
}) async {
const androidDetails = AndroidNotificationDetails(
'high_importance_channel',
'High Importance Notifications',
channelDescription: 'This channel is used for important notifications.',
importance: Importance.max,
priority: Priority.max,
showWhen: true,
playSound: true,
enableVibration: true,
enableLights: true,
fullScreenIntent: true,
icon: 'ic_notification',
);
const iosDetails = DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
);
const details = NotificationDetails(
android: androidDetails,
iOS: iosDetails,
);
await _localNotifications.show(id, title, body, details, payload: payload);
log('[FCM] Local notification shown: $title');
}
/// Ambil FCM token perangkat
Future<String?> getToken() async {
try {
return await _messaging.getToken();
} catch (e) {
log('[FCM] Failed to get token: $e');
return null;
}
}
/// Subscribe ke topic tertentu
Future<void> subscribeToTopic(String topic) async {
await _messaging.subscribeToTopic(topic);
log('[FCM] Subscribed to topic: $topic');
}
/// Unsubscribe dari topic tertentu
Future<void> unsubscribeFromTopic(String topic) async {
await _messaging.unsubscribeFromTopic(topic);
log('[FCM] Unsubscribed from topic: $topic');
}
}
+12 -12
View File
@@ -1,27 +1,27 @@
part of 'theme.dart';
class AppStyle {
static TextStyle xs = TextStyle(color: AppColor.black, fontSize: 11);
static TextStyle xs = const TextStyle(color: AppColor.black, fontSize: 11);
static TextStyle sm = TextStyle(color: AppColor.black, fontSize: 12);
static TextStyle sm = const TextStyle(color: AppColor.black, fontSize: 12);
static TextStyle md = TextStyle(color: AppColor.black, fontSize: 14);
static TextStyle md = const TextStyle(color: AppColor.black, fontSize: 14);
static TextStyle lg = TextStyle(color: AppColor.black, fontSize: 16);
static TextStyle lg = const TextStyle(color: AppColor.black, fontSize: 16);
static TextStyle xl = TextStyle(color: AppColor.black, fontSize: 18);
static TextStyle xl = const TextStyle(color: AppColor.black, fontSize: 18);
static TextStyle xxl = TextStyle(color: AppColor.black, fontSize: 20);
static TextStyle xxl = const TextStyle(color: AppColor.black, fontSize: 20);
static TextStyle h6 = TextStyle(color: AppColor.black, fontSize: 22);
static TextStyle h6 = const TextStyle(color: AppColor.black, fontSize: 22);
static TextStyle h5 = TextStyle(color: AppColor.black, fontSize: 24);
static TextStyle h5 = const TextStyle(color: AppColor.black, fontSize: 24);
static TextStyle h4 = TextStyle(color: AppColor.black, fontSize: 26);
static TextStyle h4 = const TextStyle(color: AppColor.black, fontSize: 26);
static TextStyle h3 = TextStyle(color: AppColor.black, fontSize: 28);
static TextStyle h3 = const TextStyle(color: AppColor.black, fontSize: 28);
static TextStyle h2 = TextStyle(color: AppColor.black, fontSize: 30);
static TextStyle h2 = const TextStyle(color: AppColor.black, fontSize: 30);
static TextStyle h1 = TextStyle(color: AppColor.black, fontSize: 32);
static TextStyle h1 = const TextStyle(color: AppColor.black, fontSize: 32);
}
+1 -3
View File
@@ -1,5 +1,3 @@
part of 'theme.dart';
class AppValue {
// TODO: define value
}
class AppValue {}
+6 -6
View File
@@ -11,7 +11,7 @@ class ThemeApp {
useMaterial3: true,
primaryColor: AppColor.primary,
scaffoldBackgroundColor: AppColor.white,
appBarTheme: AppBarTheme(
appBarTheme: const AppBarTheme(
color: AppColor.white,
elevation: 0,
titleTextStyle: TextStyle(
@@ -19,7 +19,7 @@ class ThemeApp {
fontSize: 16.0,
fontWeight: FontWeight.w500,
),
iconTheme: const IconThemeData(color: AppColor.primary),
iconTheme: IconThemeData(color: AppColor.primary),
),
fontFamily: FontFamily.quicksand,
colorScheme: ColorScheme.fromSeed(seedColor: AppColor.primary),
@@ -28,19 +28,19 @@ class ThemeApp {
hintStyle: const TextStyle(color: AppColor.textSecondary),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide(color: AppColor.borderDark),
borderSide: const BorderSide(color: AppColor.borderDark),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide(color: AppColor.primary),
borderSide: const BorderSide(color: AppColor.primary),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide(color: AppColor.borderDark),
borderSide: const BorderSide(color: AppColor.borderDark),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide(color: AppColor.error),
borderSide: const BorderSide(color: AppColor.error),
),
),
);
+1
View File
@@ -1,5 +1,6 @@
class ApiPath {
static const String login = '/api/v1/auth/login';
static const String logout = '/api/v1/auth/logout';
static const String outlets = '/api/v1/outlets';
static const String categories = '/api/v1/categories';
static const String products = '/api/v1/products';
@@ -89,6 +89,7 @@ class ProductAnalyticCategoryItem with _$ProductAnalyticCategoryItem {
required int totalRevenue,
}) = _ProductAnalyticCategoryItem;
// ignore: prefer_const_constructors
factory ProductAnalyticCategoryItem.empty() => ProductAnalyticCategoryItem(
categoryName: '',
productCount: 0,
@@ -4,7 +4,10 @@ abstract class IAuthRepository {
Future<Either<AuthFailure, Login>> login({
required String email,
required String password,
required Map<String, String> devicePayload,
required String fcmToken,
});
Future<Either<AuthFailure, Unit>> logout();
Future<bool> hasToken();
Future<Either<AuthFailure, User>> currentUser();
}
@@ -10,8 +10,8 @@ class ListCustomer with _$ListCustomer {
required int totalPages,
}) = _ListCustomer;
factory ListCustomer.empty() => ListCustomer(
customers: const [],
factory ListCustomer.empty() => const ListCustomer(
customers: [],
totalCount: 0,
page: 0,
limit: 0,
@@ -10,7 +10,7 @@ class AddItemOrderRequest with _$AddItemOrderRequest {
required String notes,
}) = _AddItemOrderRequest;
factory AddItemOrderRequest.empty() => AddItemOrderRequest(
factory AddItemOrderRequest.empty() => const AddItemOrderRequest(
productId: '',
productVariantId: '',
quantity: 0,
+84 -2
View File
@@ -10,12 +10,19 @@ class ListOrder with _$ListOrder {
required int totalPages,
}) = _ListOrder;
factory ListOrder.empty() =>
ListOrder(orders: [], totalCount: 0, page: 0, limit: 0, totalPages: 0);
factory ListOrder.empty() => const ListOrder(
orders: [],
totalCount: 0,
page: 0,
limit: 0,
totalPages: 0,
);
}
@freezed
class Order with _$Order {
const Order._();
const factory Order({
required String id,
required String orderNumber,
@@ -118,7 +125,10 @@ 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',
),
OrderItem(
id: 'ITEM-002',
@@ -136,7 +146,31 @@ 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',
),
OrderItem(
id: 'ITEM-003',
orderId: 'ORD-001',
productId: 'PROD-003',
productName: 'Pasta',
productVariantId: 'VAR-002',
productVariantName: '',
quantity: 1,
unitPrice: 50000,
totalPrice: 50000,
modifiers: [],
notes: '',
status: 'Served',
createdAt: DateTime.now().subtract(const Duration(hours: 1)),
updatedAt: DateTime.now(),
printerType: 'Kitchen',
printToChecker: false,
paidQuantity: 1,
categoryId: 'CAT-002',
categoryName: 'Makanan',
),
],
payments: [
@@ -161,6 +195,24 @@ class Order with _$Order {
paymentCount: 1,
splitType: 'Single',
);
// For Add Item Order
factory Order.fromLastOrder(Order order, List<ProductQuantity> products) =>
order.copyWith(
orderItems: products
.map((e) => OrderItem.fromProductQuantity(e))
.toList(),
totalAmount: products
.map((e) => e.product.price.toInt() * e.quantity)
.reduce((value, element) => value + element),
subtotal: products
.map((e) => e.product.price.toInt() * e.quantity)
.reduce((value, element) => value + element),
metadata: order.metadata,
);
List<OrderItem> get orderItemNotCancelled =>
orderItems.where((item) => item.status != 'cancelled').toList();
}
@freezed
@@ -181,7 +233,10 @@ 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,
}) = _OrderItem;
factory OrderItem.empty() => OrderItem(
@@ -200,6 +255,33 @@ class OrderItem with _$OrderItem {
createdAt: DateTime(1970),
updatedAt: DateTime(1970),
printerType: '',
printToChecker: false,
paidQuantity: 0,
categoryId: '',
categoryName: '',
);
factory OrderItem.fromProductQuantity(ProductQuantity productQuantity) =>
OrderItem(
id: '',
orderId: '',
productId: productQuantity.product.id,
productName: productQuantity.product.name,
productVariantId: productQuantity.variant?.id ?? '',
productVariantName: productQuantity.variant?.name ?? '',
quantity: productQuantity.quantity,
unitPrice: productQuantity.product.price.toInt(),
totalPrice: (productQuantity.product.price * productQuantity.quantity)
.toInt(),
modifiers: [],
notes: productQuantity.notes,
status: 'pending',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
printerType: productQuantity.product.printerType,
printToChecker: productQuantity.product.printToChecker,
paidQuantity: 0,
categoryId: '',
categoryName: '',
);
}
+1
View File
@@ -3,6 +3,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
import '../../common/api/api_failure.dart';
import '../../common/types/split_type.dart';
import '../product/product.dart';
part 'order.freezed.dart';
+75 -7
View File
@@ -681,7 +681,7 @@ class __$$OrderImplCopyWithImpl<$Res>
/// @nodoc
class _$OrderImpl implements _Order {
class _$OrderImpl extends _Order {
const _$OrderImpl({
required this.id,
required this.orderNumber,
@@ -712,7 +712,8 @@ class _$OrderImpl implements _Order {
required this.splitType,
}) : _metadata = metadata,
_orderItems = orderItems,
_payments = payments;
_payments = payments,
super._();
@override
final String id;
@@ -889,7 +890,7 @@ class _$OrderImpl implements _Order {
__$$OrderImplCopyWithImpl<_$OrderImpl>(this, _$identity);
}
abstract class _Order implements Order {
abstract class _Order extends Order {
const factory _Order({
required final String id,
required final String orderNumber,
@@ -919,6 +920,7 @@ abstract class _Order implements Order {
required final int paymentCount,
required final String splitType,
}) = _$OrderImpl;
const _Order._() : super._();
@override
String get id;
@@ -1000,7 +1002,10 @@ 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;
/// Create a copy of OrderItem
/// with the given fields replaced by the non-null parameter values.
@@ -1030,7 +1035,10 @@ abstract class $OrderItemCopyWith<$Res> {
DateTime createdAt,
DateTime updatedAt,
String printerType,
bool printToChecker,
int paidQuantity,
String categoryId,
String categoryName,
});
}
@@ -1064,7 +1072,10 @@ 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,
}) {
return _then(
_value.copyWith(
@@ -1128,10 +1139,22 @@ 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
as int,
categoryId: null == categoryId
? _value.categoryId
: categoryId // ignore: cast_nullable_to_non_nullable
as String,
categoryName: null == categoryName
? _value.categoryName
: categoryName // ignore: cast_nullable_to_non_nullable
as String,
)
as $Val,
);
@@ -1163,7 +1186,10 @@ abstract class _$$OrderItemImplCopyWith<$Res>
DateTime createdAt,
DateTime updatedAt,
String printerType,
bool printToChecker,
int paidQuantity,
String categoryId,
String categoryName,
});
}
@@ -1196,7 +1222,10 @@ class __$$OrderItemImplCopyWithImpl<$Res>
Object? createdAt = null,
Object? updatedAt = null,
Object? printerType = null,
Object? printToChecker = null,
Object? paidQuantity = null,
Object? categoryId = null,
Object? categoryName = null,
}) {
return _then(
_$OrderItemImpl(
@@ -1260,10 +1289,22 @@ 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
as int,
categoryId: null == categoryId
? _value.categoryId
: categoryId // ignore: cast_nullable_to_non_nullable
as String,
categoryName: null == categoryName
? _value.categoryName
: categoryName // ignore: cast_nullable_to_non_nullable
as String,
),
);
}
@@ -1288,7 +1329,10 @@ 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,
}) : _modifiers = modifiers;
@override
@@ -1328,11 +1372,17 @@ class _$OrderItemImpl implements _OrderItem {
@override
final String printerType;
@override
final bool printToChecker;
@override
final int paidQuantity;
@override
final String categoryId;
@override
final String categoryName;
@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)';
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
@@ -1368,12 +1418,18 @@ 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));
other.paidQuantity == paidQuantity) &&
(identical(other.categoryId, categoryId) ||
other.categoryId == categoryId) &&
(identical(other.categoryName, categoryName) ||
other.categoryName == categoryName));
}
@override
int get hashCode => Object.hash(
int get hashCode => Object.hashAll([
runtimeType,
id,
orderId,
@@ -1390,8 +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.
@@ -1419,7 +1478,10 @@ 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,
}) = _$OrderItemImpl;
@override
@@ -1453,7 +1515,13 @@ abstract class _OrderItem implements OrderItem {
@override
String get printerType;
@override
bool get printToChecker;
@override
int get paidQuantity;
@override
String get categoryId;
@override
String get categoryName;
/// Create a copy of OrderItem
/// with the given fields replaced by the non-null parameter values.
@@ -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,
});
}
@@ -10,7 +10,7 @@ class ListPaymentMethod with _$ListPaymentMethod {
required int totalPages,
}) = _ListPaymentMethod;
factory ListPaymentMethod.empty() => ListPaymentMethod(
factory ListPaymentMethod.empty() => const ListPaymentMethod(
paymentMethods: [],
totalCount: 0,
page: 0,
@@ -19,4 +19,21 @@ abstract class IPrinterRepository {
Future<Either<PrinterFailure, Unit>> printStruckSaveOrder({
required Order order,
});
Future<Either<PrinterFailure, Unit>> printStruckCashier({
required Order order,
});
Future<Either<PrinterFailure, Unit>> printStruckPayment({
required Order order,
});
Future<Either<PrinterFailure, Unit>> printStruckChecker({
required Order order,
});
Future<Either<PrinterFailure, Unit>> printStruckKitchen({
required Order order,
});
Future<Either<PrinterFailure, Unit>> printStruckBar({required Order order});
Future<Either<PrinterFailure, Unit>> printStruckVoid({required Order order});
Future<Either<PrinterFailure, Unit>> printStruckSplitBill({
required Order order,
});
}
@@ -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;
+7 -2
View File
@@ -10,8 +10,13 @@ class ListTable with _$ListTable {
required int totalPages,
}) = _ListTable;
factory ListTable.empty() =>
ListTable(tables: [], totalCount: 0, page: 0, limit: 0, totalPages: 0);
factory ListTable.empty() => const ListTable(
tables: [],
totalCount: 0,
page: 0,
limit: 0,
totalPages: 0,
);
}
@freezed
+9 -2
View File
@@ -2,6 +2,7 @@ import 'package:injectable/injectable.dart';
abstract class Env {
String get baseUrl;
String get dbName;
// add getter here...
}
@@ -9,12 +10,18 @@ abstract class Env {
@dev
class DevEnv implements Env {
@override
String get baseUrl => 'https://api-pos.apskel.id'; // example value
String get baseUrl => 'http://194.233.78.1:4001';
@override
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_prod.db";
}
@@ -36,7 +36,7 @@ class AnalyticRemoteDataProvider {
);
if (response.data['success'] == false) {
return DC.error(AnalyticFailure.unexpectedError());
return DC.error(const AnalyticFailure.unexpectedError());
}
final dashboard = DashboardAnalyticDto.fromJson(
@@ -65,7 +65,7 @@ class AnalyticRemoteDataProvider {
);
if (response.data['success'] == false) {
return DC.error(AnalyticFailure.unexpectedError());
return DC.error(const AnalyticFailure.unexpectedError());
}
final sales = SalesAnalyticDto.fromJson(
@@ -94,7 +94,7 @@ class AnalyticRemoteDataProvider {
);
if (response.data['success'] == false) {
return DC.error(AnalyticFailure.unexpectedError());
return DC.error(const AnalyticFailure.unexpectedError());
}
final products = ProductAnalyticDto.fromJson(
@@ -123,7 +123,7 @@ class AnalyticRemoteDataProvider {
);
if (response.data['success'] == false) {
return DC.error(AnalyticFailure.unexpectedError());
return DC.error(const AnalyticFailure.unexpectedError());
}
final paymentMethods = PaymentMethodAnalyticDto.fromJson(
@@ -152,7 +152,7 @@ class AnalyticRemoteDataProvider {
);
if (response.data['success'] == false) {
return DC.error(AnalyticFailure.unexpectedError());
return DC.error(const AnalyticFailure.unexpectedError());
}
final profitLoss = ProfitLossAnalyticDto.fromJson(
@@ -181,7 +181,7 @@ class AnalyticRemoteDataProvider {
);
if (response.data['success'] == false) {
return DC.error(AnalyticFailure.unexpectedError());
return DC.error(const AnalyticFailure.unexpectedError());
}
final categories = CategoryAnalyticDto.fromJson(
@@ -211,7 +211,7 @@ class AnalyticRemoteDataProvider {
);
if (response.data['success'] == false) {
return DC.error(AnalyticFailure.unexpectedError());
return DC.error(const AnalyticFailure.unexpectedError());
}
final inventory = InventoryAnalyticDto.fromJson(
@@ -53,6 +53,7 @@ class AuthLocalDataProvider {
try {
await _sharedPreferences.remove(LocalStorageKey.token);
await _sharedPreferences.remove(LocalStorageKey.user);
await _sharedPreferences.remove(LocalStorageKey.outlet);
} catch (e) {
log('deleteAllAuthError', name: _logName, error: e);
}
@@ -1,10 +1,12 @@
import 'dart:developer';
import 'package:dartz/dartz.dart';
import 'package:data_channel/data_channel.dart';
import 'package:injectable/injectable.dart';
import '../../../common/api/api_client.dart';
import '../../../common/api/api_failure.dart';
import '../../../common/function/app_function.dart';
import '../../../common/url/api_path.dart';
import '../../../domain/auth/auth.dart';
import '../auth_dtos.dart';
@@ -19,16 +21,23 @@ class AuthRemoteDataProvider {
Future<DC<AuthFailure, LoginDto>> login({
required String email,
required String password,
required Map<String, String> devicePayload,
required String fcmToken,
}) async {
try {
final response = await _apiClient.post(
ApiPath.login,
data: {'email': email, 'password': password},
data: {
'email': email,
'password': password,
'fcm_token': fcmToken,
...devicePayload,
},
);
if (response.data['code'] == 401) {
return DC.error(
AuthFailure.serverError(
const AuthFailure.serverError(
ApiFailure.unauthorized('Incorrect email or password'),
),
);
@@ -41,4 +50,22 @@ class AuthRemoteDataProvider {
return DC.error(AuthFailure.serverError(e));
}
}
Future<DC<AuthFailure, Unit>> logout() async {
try {
final response = await _apiClient.post(
ApiPath.logout,
headers: getAuthorizationHeader(),
);
if (response.data['success'] == false) {
return DC.error(const AuthFailure.unexpectedError());
}
return DC.data(unit);
} on ApiFailure catch (e, s) {
log('logout', name: _logName, error: e, stackTrace: s);
return DC.error(AuthFailure.serverError(e));
}
}
}
@@ -1,3 +1,5 @@
// ignore_for_file: prefer_const_constructors
part of '../auth_dtos.dart';
@freezed
@@ -19,11 +19,15 @@ class AuthRepository implements IAuthRepository {
Future<Either<AuthFailure, Login>> login({
required String email,
required String password,
required Map<String, String> devicePayload,
required String fcmToken,
}) async {
try {
final result = await _dataProvider.login(
email: email,
password: password,
devicePayload: devicePayload,
fcmToken: fcmToken,
);
if (result.hasError) {
@@ -57,4 +61,21 @@ class AuthRepository implements IAuthRepository {
Future<bool> hasToken() async {
return await _localDataProvider.hasToken();
}
@override
Future<Either<AuthFailure, Unit>> logout() async {
try {
final result = await _dataProvider.logout();
if (result.hasError) {
return left(result.error!);
}
await _localDataProvider.deleteAllAuth();
return right(unit);
} catch (e, s) {
log('logoutError', name: _logName, error: e, stackTrace: s);
return left(const AuthFailure.unexpectedError());
}
}
}
@@ -17,7 +17,9 @@ class CategoryLocalDataProvider {
CategoryLocalDataProvider(this._databaseHelper);
final Map<String, List<CategoryDto>> _queryCache = {};
final Duration _cacheExpiry = Duration(minutes: AppConstant.cacheExpire);
final Duration _cacheExpiry = const Duration(
minutes: AppConstant.cacheExpire,
);
final Map<String, DateTime> _cacheTimestamps = {};
Future<DC<CategoryFailure, void>> saveCategoriesBatch(
@@ -203,7 +205,7 @@ class CategoryLocalDataProvider {
if (maps.isEmpty) {
log('❌ Category not found: $id', name: _logName);
return DC.error(CategoryFailure.empty());
return DC.error(const CategoryFailure.empty());
}
final category = CategoryDto.fromMap(maps.first);
@@ -29,7 +29,7 @@ class CategoryRemoteDataProvider {
);
if (response.data['data'] == null) {
return DC.error(CategoryFailure.empty());
return DC.error(const CategoryFailure.empty());
}
final categories = ListCategoryDto.fromJson(
@@ -63,7 +63,7 @@ class CategoryDto with _$CategoryDto {
'name': name,
'description': description,
'business_type': businessType,
'order': order,
'sort_order': order,
'metadata': metadata != null ? jsonEncode(metadata) : null,
'created_at': createdAt,
'updated_at': updatedAt,
@@ -29,7 +29,7 @@ class CustomerRemoteDataProvider {
);
if (response.data['success'] == false) {
return DC.error(CustomerFailure.unexpectedError());
return DC.error(const CustomerFailure.unexpectedError());
}
final customers = ListCustomerDto.fromJson(
@@ -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,
@@ -46,7 +55,7 @@ class OrderRemoteDataProvider {
);
if (response.data['success'] == false) {
return DC.error(OrderFailure.unexpectedError());
return DC.error(const OrderFailure.unexpectedError());
}
final orders = ListOrderDto.fromJson(
@@ -68,7 +77,7 @@ class OrderRemoteDataProvider {
);
if (response.data['success'] == false) {
return DC.error(OrderFailure.unexpectedError());
return DC.error(const OrderFailure.unexpectedError());
}
final order = OrderDto.fromJson(
@@ -93,7 +102,7 @@ class OrderRemoteDataProvider {
);
if (response.data['success'] == false) {
return DC.error(OrderFailure.unexpectedError());
return DC.error(const OrderFailure.unexpectedError());
}
final order = OrderDto.fromJson(
@@ -119,7 +128,7 @@ class OrderRemoteDataProvider {
);
if (response.data['success'] == false) {
return DC.error(OrderFailure.unexpectedError());
return DC.error(const OrderFailure.unexpectedError());
} else {
final order = OrderDto.fromJson(
response.data['data'] as Map<String, dynamic>,
@@ -151,17 +160,18 @@ 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) {
return DC.error(OrderFailure.unexpectedError());
return DC.error(const OrderFailure.unexpectedError());
}
final payment = PaymentDto.fromJson(
@@ -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,15 +197,15 @@ class OrderRemoteDataProvider {
'notes': '',
'order_items': request.map((e) => e.toRequest()).toList(),
},
headers: getAuthorizationHeader(),
headers: _buildHeaders(idempotencyKey: idempotencyKey),
);
if (response.data['success'] == false) {
return DC.error(OrderFailure.unexpectedError());
return DC.error(const OrderFailure.unexpectedError());
}
final order = OrderDto.fromJson(
response.data['data'] as Map<String, dynamic>,
response.data['data']['updated_order'] as Map<String, dynamic>,
);
return DC.data(order);
@@ -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,11 +235,11 @@ class OrderRemoteDataProvider {
)
.toList(),
},
headers: getAuthorizationHeader(),
headers: _buildHeaders(idempotencyKey: idempotencyKey),
);
if (response.data['success'] == false) {
return DC.error(OrderFailure.unexpectedError());
return DC.error(const OrderFailure.unexpectedError());
}
return DC.data(unit);
@@ -238,18 +250,19 @@ 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) {
return DC.error(OrderFailure.unexpectedError());
return DC.error(const OrderFailure.unexpectedError());
}
final payment = PaymentDto.fromJson(
@@ -267,16 +280,17 @@ 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) {
return DC.error(OrderFailure.unexpectedError());
return DC.error(const OrderFailure.unexpectedError());
}
return DC.data(unit);
@@ -114,7 +114,10 @@ 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,
}) = _OrderItemDto;
factory OrderItemDto.fromJson(Map<String, dynamic> json) =>
@@ -137,7 +140,10 @@ 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 ?? '',
);
factory OrderItemDto.fromDomain(OrderItem orderItem) => OrderItemDto(
@@ -156,6 +162,9 @@ 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,8 +1169,14 @@ 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")
String? get categoryId => throw _privateConstructorUsedError;
@JsonKey(name: "category_name")
String? get categoryName => throw _privateConstructorUsedError;
/// Serializes this OrderItemDto to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@@ -1205,7 +1211,10 @@ 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,
});
}
@@ -1239,7 +1248,10 @@ 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,
}) {
return _then(
_value.copyWith(
@@ -1303,10 +1315,22 @@ 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
as int?,
categoryId: freezed == categoryId
? _value.categoryId
: categoryId // ignore: cast_nullable_to_non_nullable
as String?,
categoryName: freezed == categoryName
? _value.categoryName
: categoryName // ignore: cast_nullable_to_non_nullable
as String?,
)
as $Val,
);
@@ -1338,7 +1362,10 @@ 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,
});
}
@@ -1371,7 +1398,10 @@ class __$$OrderItemDtoImplCopyWithImpl<$Res>
Object? createdAt = freezed,
Object? updatedAt = freezed,
Object? printerType = freezed,
Object? printToChecker = freezed,
Object? paidQuantity = freezed,
Object? categoryId = freezed,
Object? categoryName = freezed,
}) {
return _then(
_$OrderItemDtoImpl(
@@ -1435,10 +1465,22 @@ 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
as int?,
categoryId: freezed == categoryId
? _value.categoryId
: categoryId // ignore: cast_nullable_to_non_nullable
as String?,
categoryName: freezed == categoryName
? _value.categoryName
: categoryName // ignore: cast_nullable_to_non_nullable
as String?,
),
);
}
@@ -1463,7 +1505,10 @@ 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,
}) : _modifiers = modifiers,
super._();
@@ -1524,12 +1569,21 @@ 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
@JsonKey(name: "category_id")
final String? categoryId;
@override
@JsonKey(name: "category_name")
final String? categoryName;
@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)';
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
@@ -1565,13 +1619,19 @@ 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));
other.paidQuantity == paidQuantity) &&
(identical(other.categoryId, categoryId) ||
other.categoryId == categoryId) &&
(identical(other.categoryName, categoryName) ||
other.categoryName == categoryName));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
int get hashCode => Object.hashAll([
runtimeType,
id,
orderId,
@@ -1588,8 +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.
@@ -1622,7 +1685,10 @@ 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,
}) = _$OrderItemDtoImpl;
const _OrderItemDto._() : super._();
@@ -1675,8 +1741,17 @@ 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
@JsonKey(name: "category_id")
String? get categoryId;
@override
@JsonKey(name: "category_name")
String? get categoryName;
/// Create a copy of OrderItemDto
/// with the given fields replaced by the non-null parameter values.
@@ -109,7 +109,10 @@ _$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?,
);
Map<String, dynamic> _$$OrderItemDtoImplToJson(_$OrderItemDtoImpl instance) =>
@@ -129,7 +132,10 @@ 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,
};
_$OrderRequestDtoImpl _$$OrderRequestDtoImplFromJson(
@@ -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!);
@@ -29,7 +29,7 @@ class OutletRemoteDataProvider {
);
if (response.data['data'] == null) {
return DC.error(OutletFailure.empty());
return DC.error(const OutletFailure.empty());
}
final outlets = (response.data['data']['outlets'] as List)
@@ -51,7 +51,7 @@ class OutletRemoteDataProvider {
);
if (response.data['data'] == null) {
return DC.error(OutletFailure.empty());
return DC.error(const OutletFailure.empty());
}
final outlet = OutletDto.fromJson(
@@ -29,7 +29,7 @@ class PaymentMethodRemoteDataProvider {
);
if (response.data['success'] == false) {
return DC.error(PaymentMethodFailure.unexpectedError());
return DC.error(const PaymentMethodFailure.unexpectedError());
}
final paymentMethods = ListPaymentMethodDto.fromJson(
@@ -26,7 +26,7 @@ class PrinterLocalDataProvider {
if (printerExist.hasData) {
return DC.error(
PrinterFailure.dynamicErrorMessage('Printer Telah Terdaftar'),
const PrinterFailure.dynamicErrorMessage('Printer Telah Terdaftar'),
);
}
@@ -43,7 +43,7 @@ class PrinterLocalDataProvider {
log('Error creating printer', name: _logName, error: e);
return DC.error(
PrinterFailure.dynamicErrorMessage('Error creating printer'),
const PrinterFailure.dynamicErrorMessage('Error creating printer'),
);
}
}
@@ -65,7 +65,7 @@ class PrinterLocalDataProvider {
if (updatedRows == 0) {
return DC.error(
PrinterFailure.dynamicErrorMessage('Printer not found'),
const PrinterFailure.dynamicErrorMessage('Printer not found'),
);
}
@@ -76,7 +76,7 @@ class PrinterLocalDataProvider {
log('Error updating printer', name: _logName, error: e);
return DC.error(
PrinterFailure.dynamicErrorMessage('Error updating printer'),
const PrinterFailure.dynamicErrorMessage('Error updating printer'),
);
}
}
@@ -94,7 +94,7 @@ class PrinterLocalDataProvider {
if (deletedRows == 0) {
return DC.error(
PrinterFailure.dynamicErrorMessage('Printer not found'),
const PrinterFailure.dynamicErrorMessage('Printer not found'),
);
}
@@ -105,7 +105,7 @@ class PrinterLocalDataProvider {
log('Error deleting printer', name: _logName, error: e);
return DC.error(
PrinterFailure.dynamicErrorMessage('Error deleting printer'),
const PrinterFailure.dynamicErrorMessage('Error deleting printer'),
);
}
}
@@ -123,7 +123,7 @@ class PrinterLocalDataProvider {
if (result.isEmpty) {
log('Printer with code $code not found');
return DC.error(PrinterFailure.empty());
return DC.error(const PrinterFailure.empty());
}
final printer = PrinterDto.fromJson(result.first);
@@ -133,7 +133,7 @@ class PrinterLocalDataProvider {
log('findPrinterByCode', name: _logName, error: e);
return DC.error(
PrinterFailure.dynamicErrorMessage('Error getting printer'),
const PrinterFailure.dynamicErrorMessage('Error getting printer'),
);
}
}
@@ -15,8 +15,19 @@ class PrinterDto with _$PrinterDto {
@JsonKey(name: 'updated_at') required DateTime updatedAt,
}) = _PrinterDto;
factory PrinterDto.fromJson(Map<String, dynamic> json) =>
_$PrinterDtoFromJson(json);
factory PrinterDto.fromJson(Map<String, dynamic> json) => _$PrinterDtoFromJson(
// Normalize paper field: SQLite bisa return int atau String
{
...json,
'paper': json['paper']?.toString() ?? '58',
'created_at': json['created_at'] is DateTime
? (json['created_at'] as DateTime).toIso8601String()
: json['created_at'],
'updated_at': json['updated_at'] is DateTime
? (json['updated_at'] as DateTime).toIso8601String()
: json['updated_at'],
},
);
// Optional mapper to domain
Printer toDomain() => Printer(
@@ -5,7 +5,9 @@ import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter_esc_pos_network/flutter_esc_pos_network.dart';
import 'package:injectable/injectable.dart' hide Order;
import 'package:print_bluetooth_thermal/print_bluetooth_thermal.dart';
import 'package:synchronized/synchronized.dart';
import '../../../common/extension/extension.dart';
import '../../../domain/order/order.dart';
import '../../../domain/outlet/outlet.dart';
import '../../../domain/printer/printer.dart';
@@ -28,6 +30,14 @@ class PrinterRepository implements IPrinterRepository {
this._authLocalDataProvider,
);
final Map<String, Lock> _locks = {};
final Lock _globalPrintLock = Lock();
String? _connectedMac;
Lock _getLock(String address) {
return _locks.putIfAbsent(address, () => Lock());
}
@override
Future<Either<PrinterFailure, bool>> connectBluetooth(
String macAddress,
@@ -78,7 +88,7 @@ class PrinterRepository implements IPrinterRepository {
);
log("Error connecting to Bluetooth printer", name: _logName, error: e);
return left(
PrinterFailure.dynamicErrorMessage(
const PrinterFailure.dynamicErrorMessage(
'Error connecting to Bluetooth printer',
),
);
@@ -94,7 +104,7 @@ class PrinterRepository implements IPrinterRepository {
} catch (e) {
log("Error disconnecting Bluetooth printer", error: e, name: _logName);
return left(
PrinterFailure.dynamicErrorMessage(
const PrinterFailure.dynamicErrorMessage(
'Error disconnecting Bluetooth printer',
),
);
@@ -113,7 +123,7 @@ class PrinterRepository implements IPrinterRepository {
} catch (e) {
log("Error getting paired Bluetooth devices", name: _logName, error: e);
return left(
PrinterFailure.dynamicErrorMessage(
const PrinterFailure.dynamicErrorMessage(
'Error getting paired Bluetooth devices',
),
);
@@ -129,7 +139,9 @@ class PrinterRepository implements IPrinterRepository {
} catch (e) {
log("Error checking Bluetooth status", name: _logName, error: e);
return left(
PrinterFailure.dynamicErrorMessage('Error checking Bluetooth status'),
const PrinterFailure.dynamicErrorMessage(
'Error checking Bluetooth status',
),
);
}
}
@@ -213,62 +225,98 @@ class PrinterRepository implements IPrinterRepository {
Printer printer,
List<int> printData,
) async {
try {
log(printer.toString());
if (printer.type == 'Bluetooth') {
final connected = await connectBluetooth(printer.address);
if (printer.type == 'Bluetooth') {
return await _getLock(printer.address).synchronized(() async {
try {
bool isConnected = await PrintBluetoothThermal.connectionStatus;
connected.fold(
(f) {
return left(
PrinterFailure.dynamicErrorMessage(
'Printer cannot connect to bluetooth, Please connect in printer setting!',
),
// Hanya reconnect jika memang perlu
if (!isConnected || _connectedMac != printer.address) {
// Disconnect hanya jika terhubung ke printer LAIN
if (isConnected && _connectedMac != printer.address) {
await PrintBluetoothThermal.disconnect;
await Future.delayed(
const Duration(milliseconds: 300),
); // dari 1500ms
}
bool connected = await PrintBluetoothThermal.connect(
macPrinterAddress: printer.address,
);
},
(connect) async {
if (!connect) {
if (!connected) {
_connectedMac = null;
FirebaseCrashlytics.instance.recordError(
'Failed to connect to Bluetooth printer',
null,
reason: 'Failed to connect to Bluetooth print',
information: [
'function: printStruct(Printer printer, List<int> printValue,)',
'function: printStruct(Printer printer, List<int> printData)',
'Printer: ${printer.name}',
'macAddress: ${printer.address}',
'in: $_logName ',
'in: $_logName',
],
);
return left(
PrinterFailure.dynamicErrorMessage(
const PrinterFailure.dynamicErrorMessage(
'Printer cannot connect to bluetooth, Please connect in printer setting!',
),
);
}
bool printResult = await _printBluetooth(printData);
if (!printResult) {
FirebaseCrashlytics.instance.recordError(
'Failed to print to ${printer.name}',
null,
information: [
'function: await printBluetooth(printData);',
'print: $printData',
'in: $_logName',
],
);
return left(
PrinterFailure.dynamicErrorMessage(
'Failed to print to ${printer.name}',
),
);
}
_connectedMac = printer.address;
await Future.delayed(
const Duration(milliseconds: 300),
); // dari 1000ms
}
return right(printResult);
},
);
} else {
log(
'Printer connected to bluetooth: ${printer.name}, ${printer.address}',
name: _logName,
);
bool printResult = await _writeBytesChunked(printData);
// Hapus delay 1000ms setelah print — tidak perlu untuk hasil cetak
log("Print result ${printer.name}: $printResult");
if (!printResult) {
FirebaseCrashlytics.instance.recordError(
'Failed to print to ${printer.name}',
null,
information: [
'function: await _writeBytesChunked(printData)',
'print: $printData',
'in: $_logName',
],
);
return left(
PrinterFailure.dynamicErrorMessage(
'Failed to print to ${printer.name}',
),
);
}
return right(printResult);
} catch (e, stackTrace) {
_connectedMac = null;
FirebaseCrashlytics.instance.recordError(
e,
stackTrace,
reason: 'Error printing ${printer.name}',
information: [
'Printer: ${printer.name}',
'MAC: ${printer.address}',
],
);
return left(
const PrinterFailure.dynamicErrorMessage('Error printing struct'),
);
}
});
} else {
// Network printer tetap sama
try {
bool printResult = await _printNetwork(printer.address, printData);
if (!printResult) {
@@ -277,7 +325,7 @@ class PrinterRepository implements IPrinterRepository {
null,
reason: 'Failed to connect to Network Printer',
information: [
'function: await printNetwork(printer.address, printData);',
'function: await _printNetwork(printer.address, printData)',
'Printer: ${printer.name}',
'ipAddress: ${printer.address}',
'print: $printData',
@@ -285,63 +333,72 @@ class PrinterRepository implements IPrinterRepository {
],
);
}
return right(printResult);
}
return right(false);
} catch (e) {
log("Error printing struct", name: _logName, error: e);
return left(PrinterFailure.dynamicErrorMessage('Error printing struct'));
}
}
Future<bool> _printBluetooth(List<int> printData) async {
try {
bool isConnected = await PrintBluetoothThermal.connectionStatus;
if (!isConnected) {
log("Not connected to Bluetooth printer", name: _logName);
return false;
}
bool printResult = await PrintBluetoothThermal.writeBytes(printData);
if (printResult) {
log("Successfully printed via Bluetooth", name: _logName);
} else {
} catch (e, stackTrace) {
FirebaseCrashlytics.instance.recordError(
'Failed to print via Bluetooth',
null,
reason: 'Failed to print via Bluetooth',
information: [
'function: printBluetooth(List<int> printData)',
'printData: $printData',
'in: $_logName',
],
e,
stackTrace,
reason: 'Error printing ${printer.name}',
information: ['Printer: ${printer.name}', 'IP: ${printer.address}'],
);
return left(
const PrinterFailure.dynamicErrorMessage('Error printing struct'),
);
log("Failed to print via Bluetooth", name: _logName);
}
return printResult;
} catch (e, stackTrace) {
FirebaseCrashlytics.instance.recordError(
e,
stackTrace,
reason: 'Error printing via Bluetooth',
information: [
'function: printBluetooth(List<int> printData)',
'Printer: Bluetooth printer',
'printData: $printData',
'in: $_logName',
],
);
log("Error printing via Bluetooth: $e", name: _logName);
return false;
}
}
// Future<bool> _printBluetooth(List<int> printData) async {
// try {
// bool isConnected = await PrintBluetoothThermal.connectionStatus;
// if (!isConnected) {
// log("Not connected to Bluetooth printer", name: _logName);
// return false;
// }
// bool printResult = await PrintBluetoothThermal.writeBytes(printData);
// if (printResult) {
// log("Successfully printed via Bluetooth", name: _logName);
// } else {
// FirebaseCrashlytics.instance.recordError(
// 'Failed to print via Bluetooth',
// null,
// reason: 'Failed to print via Bluetooth',
// information: [
// 'function: printBluetooth(List<int> printData)',
// 'printData: $printData',
// 'in: $_logName',
// ],
// );
// log("Failed to print via Bluetooth", name: _logName);
// }
// return printResult;
// } catch (e, stackTrace) {
// FirebaseCrashlytics.instance.recordError(
// e,
// stackTrace,
// reason: 'Error printing via Bluetooth',
// information: [
// 'function: printBluetooth(List<int> printData)',
// 'Printer: Bluetooth printer',
// 'printData: $printData',
// 'in: $_logName',
// ],
// );
// log("Error printing via Bluetooth: $e", name: _logName);
// return false;
// }
// }
Future<bool> _printNetwork(String ipAddress, List<int> printData) async {
try {
final printer = PrinterNetworkManager(ipAddress);
final printer = PrinterNetworkManager(
ipAddress,
port: 9100,
timeout: const Duration(seconds: 15),
);
PosPrintResult connect = await printer.connect();
if (connect == PosPrintResult.success) {
@@ -402,82 +459,199 @@ class PrinterRepository implements IPrinterRepository {
Future<Either<PrinterFailure, Unit>> printStruckOrder({
required Order order,
}) async {
try {
final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser();
return await _globalPrintLock.synchronized(() async {
try {
final outlet = await _outletLocalDatasource.currentOutlet();
final user = await _authLocalDataProvider.currentUser();
// Struck for customer
if (order.totalPaid > 0) {
_printReceipt(order: order, outlet: outlet, cashieName: user.name);
// Struck for 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 (cashierResult.isLeft()) return cashierResult;
// Struck for checker
final checkerResult = await _printChecker(
order: order,
outlet: outlet,
cashieName: user.name,
);
if (checkerResult.isLeft()) return checkerResult;
// 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'),
);
}
// Struck for cashier
_printCashier(order: order, outlet: outlet, cashieName: user.name);
// Struck for kitchen
_printKitchen(order: order, outlet: outlet, cashieName: user.name);
// Struck for checker
_printChecker(order: order, outlet: outlet, cashieName: user.name);
// Struck for bar if exist product bar
final itemsBar = order.orderItems.where(
(item) => item.printerType == 'bar',
);
if (itemsBar.isNotEmpty) {
_printBar(order: order, outlet: outlet, cashieName: user.name);
}
return right(unit);
} catch (e, stackTrace) {
FirebaseCrashlytics.instance.recordError(
'Print Struck Order Error: $e',
stackTrace,
reason:
'Print struck order error / Printer not setting in printer page',
information: ['Order ID: ${order.id}'],
);
log("Error printing struck order", name: _logName, error: e);
return left(
const PrinterFailure.dynamicErrorMessage('Error printing order'),
);
}
}); // end _globalPrintLock.synchronized
}
@override
Future<Either<PrinterFailure, Unit>> printStruckSaveOrder({
required Order order,
}) async {
try {
return await _globalPrintLock.synchronized(() async {
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 checker
final checkerResult = await _printChecker(
order: order,
outlet: outlet,
cashieName: user.name,
);
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'),
);
}
}); // end _globalPrintLock.synchronized
}
@override
Future<Either<PrinterFailure, Unit>> printStruckCashier({
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);
});
}
// Struck for cashier
_printCashier(order: order, outlet: outlet, cashieName: user.name);
// Struck for kitchen
_printKitchen(order: order, outlet: outlet, cashieName: user.name);
// Struck for checker
_printChecker(order: order, outlet: outlet, cashieName: user.name);
// Struck for bar if exist product bar
final itemsBar = order.orderItems.where(
(item) => item.printerType == 'bar',
);
@override
Future<Either<PrinterFailure, Unit>> printStruckVoid({
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);
});
}
if (itemsBar.isNotEmpty) {
_printBar(order: order, outlet: outlet, cashieName: user.name);
}
@override
Future<Either<PrinterFailure, Unit>> printStruckSplitBill({
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,
);
});
}
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'),
);
}
@override
Future<Either<PrinterFailure, Unit>> printStruckPayment({
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);
});
}
Future<Either<PrinterFailure, Unit>> _printReceipt({
@@ -497,11 +671,14 @@ class PrinterRepository implements IPrinterRepository {
order: order,
outlet: outlet,
cashierName: cashieName,
paper: printer.paper.toIntegerFromText,
);
await printStruct(printer, printValue);
final printResult = await printStruct(printer, printValue);
if (printResult.isLeft()) return printResult.map((_) => unit);
log('Finished printed receipt', name: _logName);
log('Printer Size: ', name: printer.paper);
return right(unit);
} catch (e, stackTrace) {
@@ -520,6 +697,7 @@ class PrinterRepository implements IPrinterRepository {
);
}
} else {
log('Receipt printer not found', name: _logName);
FirebaseCrashlytics.instance.recordError(
'Receipt printer not found',
null,
@@ -528,7 +706,9 @@ class PrinterRepository implements IPrinterRepository {
information: ['Order ID: ${order.id}'],
);
return left(
const PrinterFailure.dynamicErrorMessage('Receipt printer not found'),
const PrinterFailure.dynamicErrorMessage(
'Receipt printer belum terhubung, Silahkan hubungkan printer di pengaturan.',
),
);
}
}
@@ -550,9 +730,11 @@ class PrinterRepository implements IPrinterRepository {
order: order,
outlet: outlet,
cashierName: cashieName,
paper: printer.paper.toIntegerFromText,
);
await printStruct(printer, printValue);
final printResult = await printStruct(printer, printValue);
if (printResult.isLeft()) return printResult.map((_) => unit);
log('Finished printing cashier', name: _logName);
@@ -581,7 +763,9 @@ class PrinterRepository implements IPrinterRepository {
information: ['Order ID: ${order.id}'],
);
return left(
const PrinterFailure.dynamicErrorMessage('Cashier printer not found'),
const PrinterFailure.dynamicErrorMessage(
'Receipt printer belum terhubung, Silahkan hubungkan printer di pengaturan.',
),
);
}
}
@@ -597,15 +781,25 @@ class PrinterRepository implements IPrinterRepository {
);
if (kitchenPrinter.hasData) {
try {
final printer = kitchenPrinter.data!.toDomain();
final productKitchen = order.orderItems
.where((item) => item.printerType == 'kitchen')
.toList();
final printValue = await PrintUi().printKitchen(
order: order,
outlet: outlet,
cashierName: cashieName,
);
if (productKitchen.isNotEmpty) {
final printer = kitchenPrinter.data!.toDomain();
await printStruct(printer, printValue);
final printValue = await PrintUi().printKitchen(
order: order.copyWith(orderItems: productKitchen),
outlet: outlet,
cashierName: cashieName,
paper: printer.paper.toIntegerFromText,
);
final printResult = await printStruct(printer, printValue);
if (printResult.isLeft()) return printResult.map((_) => unit);
} else {
log('Product with printer type bar not found', name: _logName);
}
log('Finished printed kitchen', name: _logName);
@@ -634,7 +828,9 @@ class PrinterRepository implements IPrinterRepository {
information: ['Order ID: ${order.id}'],
);
return left(
const PrinterFailure.dynamicErrorMessage('Kitchen printer not found'),
const PrinterFailure.dynamicErrorMessage(
'Kitchen printer belum terhubung, Silahkan hubungkan printer di pengaturan.',
),
);
}
}
@@ -656,9 +852,11 @@ class PrinterRepository implements IPrinterRepository {
order: order,
outlet: outlet,
cashierName: cashieName,
paper: printer.paper.toIntegerFromText,
);
await printStruct(printer, printValue);
final printResult = await printStruct(printer, printValue);
if (printResult.isLeft()) return printResult.map((_) => unit);
log('Finished printed checker', name: _logName);
@@ -679,6 +877,7 @@ class PrinterRepository implements IPrinterRepository {
);
}
} else {
log('Checker printer not found', name: _logName);
FirebaseCrashlytics.instance.recordError(
'Checker printer not found',
null,
@@ -687,7 +886,9 @@ class PrinterRepository implements IPrinterRepository {
information: ['Order ID: ${order.id}'],
);
return left(
const PrinterFailure.dynamicErrorMessage('Checker printer not found'),
const PrinterFailure.dynamicErrorMessage(
'Checker printer belum terhubung, Silahkan hubungkan printer di pengaturan.',
),
);
}
}
@@ -709,12 +910,14 @@ class PrinterRepository implements IPrinterRepository {
final printer = barPrinter.data!.toDomain();
final printValue = await PrintUi().printBar(
order: order,
order: order.copyWith(orderItems: productBar),
outlet: outlet,
cashierName: cashieName,
paper: printer.paper.toIntegerFromText,
);
await printStruct(printer, printValue);
final printResult = await printStruct(printer, printValue);
if (printResult.isLeft()) return printResult.map((_) => unit);
log('Finished printed bar', name: _logName);
} else {
@@ -744,8 +947,163 @@ class PrinterRepository implements IPrinterRepository {
information: ['Order ID: ${order.id}'],
);
return left(
const PrinterFailure.dynamicErrorMessage('Bar printer not found'),
const PrinterFailure.dynamicErrorMessage(
'Bar printer belum terhubung, Silahkan hubungkan printer di pengaturan.',
),
);
}
}
Future<Either<PrinterFailure, Unit>> _printVoid({
required Order order,
required Outlet outlet,
required String cashieName,
}) async {
log('Starting to print void', name: _logName);
final voidPrinter = await _localDataProvider.findPrinterByCode('receipt');
if (voidPrinter.hasData) {
try {
final printer = voidPrinter.data!.toDomain();
final printValue = await PrintUi().printVoid(
order: order,
outlet: outlet,
cashierName: cashieName,
paper: printer.paper.toIntegerFromText,
);
final printResult = await printStruct(printer, printValue);
if (printResult.isLeft()) return printResult.map((_) => unit);
log('Finished printed void', name: _logName);
return right(unit);
} catch (e, stackTrace) {
FirebaseCrashlytics.instance.recordError(
'Print Struck Void Error: $e',
stackTrace,
reason:
'Print struck void error / Printer not setting in printer page',
information: ['Order ID: ${order.id}'],
);
log("Error printing void", name: _logName, error: e);
return left(
const PrinterFailure.dynamicErrorMessage('Error printing void order'),
);
}
} else {
FirebaseCrashlytics.instance.recordError(
'Void printer not found',
null,
reason: 'Void printer not found / Printer not setting in printer page',
information: ['Order ID: ${order.id}'],
);
return left(
const PrinterFailure.dynamicErrorMessage(
'Receipt printer belum terhubung, Silahkan hubungkan printer di pengaturan.',
),
);
}
}
Future<Either<PrinterFailure, Unit>> _printSplitBill({
required Order order,
required Outlet outlet,
required String cashieName,
}) async {
log('Starting to print splitBill', name: _logName);
final splitBillPrinter = await _localDataProvider.findPrinterByCode(
'receipt',
);
if (splitBillPrinter.hasData) {
try {
final printer = splitBillPrinter.data!.toDomain();
final printValue = await PrintUi().printSplit(
order: order,
outlet: outlet,
cashierName: cashieName,
paper: printer.paper.toIntegerFromText,
);
final printResult = await printStruct(printer, printValue);
if (printResult.isLeft()) return printResult.map((_) => unit);
log('Finished printed splitBill', name: _logName);
return right(unit);
} catch (e, stackTrace) {
FirebaseCrashlytics.instance.recordError(
'Print Struck SplitBill Error: $e',
stackTrace,
reason:
'Print struck splitBill error / Printer not setting in printer page',
information: ['Order ID: ${order.id}'],
);
log("Error printing splitBill", name: _logName, error: e);
return left(
const PrinterFailure.dynamicErrorMessage(
'Error printing splitBill order',
),
);
}
} else {
FirebaseCrashlytics.instance.recordError(
'SplitBill printer not found',
null,
reason:
'SplitBill printer not found / Printer not setting in printer page',
information: ['Order ID: ${order.id}'],
);
return left(
const PrinterFailure.dynamicErrorMessage(
'Receipt printer belum terhubung, Silahkan hubungkan printer di pengaturan.',
),
);
}
}
Future<bool> _writeBytesChunked(List<int> data, {int chunkSize = 512}) async {
for (int i = 0; i < data.length; i += chunkSize) {
final end = (i + chunkSize > data.length) ? data.length : i + chunkSize;
final chunk = data.sublist(i, end);
bool result = await PrintBluetoothThermal.writeBytes(chunk);
if (!result) return false;
await Future.delayed(const Duration(milliseconds: 100));
}
return true;
}
@override
Future<Either<PrinterFailure, Unit>> printStruckBar({
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);
});
}
@override
Future<Either<PrinterFailure, Unit>> printStruckChecker({
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);
});
}
@override
Future<Either<PrinterFailure, Unit>> printStruckKitchen({
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);
});
}
}
@@ -17,7 +17,9 @@ class ProductLocalDataProvider {
final _logName = 'ProductLocalDataProvider';
final Map<String, List<ProductDto>> _queryCache = {};
final Duration _cacheExpiry = Duration(minutes: AppConstant.cacheExpire);
final Duration _cacheExpiry = const Duration(
minutes: AppConstant.cacheExpire,
);
final Map<String, DateTime> _cacheTimestamps = {};
ProductLocalDataProvider(this._databaseHelper);
@@ -281,7 +283,7 @@ class ProductLocalDataProvider {
if (maps.isEmpty) {
log('❌ Product not found: $id', name: _logName);
return DC.error(ProductFailure.empty());
return DC.error(const ProductFailure.empty());
}
final variants = await _getProductVariants(db, id);
@@ -41,7 +41,7 @@ class ProductRemoteDataProvider {
);
if (response.data['data'] == null) {
return DC.error(ProductFailure.empty());
return DC.error(const ProductFailure.empty());
}
final categories = ListProductDto.fromJson(
@@ -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,
@@ -38,7 +38,7 @@ class TableRemoteDataProvider {
);
if (response.data['data'] == null) {
return DC.error(TableFailure.empty());
return DC.error(const TableFailure.empty());
}
final tables = ListTableDto.fromJson(
@@ -71,7 +71,7 @@ class TableRemoteDataProvider {
);
if (response.data['success'] == false) {
return DC.error(TableFailure.unexpectedError());
return DC.error(const TableFailure.unexpectedError());
}
final table = TableDto.fromJson(
@@ -99,7 +99,7 @@ class TableRemoteDataProvider {
);
if (response.data['success'] == false) {
return DC.error(TableFailure.unexpectedError());
return DC.error(const TableFailure.unexpectedError());
}
final table = TableDto.fromJson(
@@ -124,7 +124,7 @@ class TableRemoteDataProvider {
);
if (response.data['success'] == false) {
return DC.error(TableFailure.unexpectedError());
return DC.error(const TableFailure.unexpectedError());
}
return DC.data(unit);
+94 -44
View File
@@ -26,6 +26,8 @@ import 'package:apskel_pos_flutter_v2/application/analytic/sales_analytic_loader
import 'package:apskel_pos_flutter_v2/application/auth/auth_bloc.dart' as _i343;
import 'package:apskel_pos_flutter_v2/application/auth/login_form/login_form_bloc.dart'
as _i46;
import 'package:apskel_pos_flutter_v2/application/auth/logout/logout_bloc.dart'
as _i641;
import 'package:apskel_pos_flutter_v2/application/category/category_loader/category_loader_bloc.dart'
as _i1018;
import 'package:apskel_pos_flutter_v2/application/checkout/checkout_form/checkout_form_bloc.dart'
@@ -63,6 +65,8 @@ import 'package:apskel_pos_flutter_v2/application/report/report_bloc.dart'
import 'package:apskel_pos_flutter_v2/application/split_bill/split_bill_form/split_bill_form_bloc.dart'
as _i334;
import 'package:apskel_pos_flutter_v2/application/sync/sync_bloc.dart' as _i741;
import 'package:apskel_pos_flutter_v2/application/sync/sync_setting/sync_setting_bloc.dart'
as _i729;
import 'package:apskel_pos_flutter_v2/application/table/table_form/table_form_bloc.dart'
as _i248;
import 'package:apskel_pos_flutter_v2/application/table/table_loader/table_loader_bloc.dart'
@@ -76,10 +80,14 @@ import 'package:apskel_pos_flutter_v2/common/di/di_auto_route.dart' as _i729;
import 'package:apskel_pos_flutter_v2/common/di/di_connectivity.dart' as _i807;
import 'package:apskel_pos_flutter_v2/common/di/di_database.dart' as _i209;
import 'package:apskel_pos_flutter_v2/common/di/di_dio.dart' as _i86;
import 'package:apskel_pos_flutter_v2/common/di/di_firebase.dart' as _i857;
import 'package:apskel_pos_flutter_v2/common/di/di_shared_preferences.dart'
as _i135;
import 'package:apskel_pos_flutter_v2/common/network/network_client.dart'
as _i171;
import 'package:apskel_pos_flutter_v2/common/service/device_info_service.dart'
as _i288;
import 'package:apskel_pos_flutter_v2/common/service/fcm_service.dart' as _i312;
import 'package:apskel_pos_flutter_v2/domain/analytic/analytic.dart' as _i346;
import 'package:apskel_pos_flutter_v2/domain/auth/auth.dart' as _i776;
import 'package:apskel_pos_flutter_v2/domain/category/category.dart' as _i502;
@@ -143,9 +151,14 @@ import 'package:apskel_pos_flutter_v2/infrastructure/table/repositories/table_re
import 'package:apskel_pos_flutter_v2/presentation/router/app_router.dart'
as _i800;
import 'package:connectivity_plus/connectivity_plus.dart' as _i895;
import 'package:device_info_plus/device_info_plus.dart' as _i833;
import 'package:dio/dio.dart' as _i361;
import 'package:firebase_messaging/firebase_messaging.dart' as _i892;
import 'package:flutter_local_notifications/flutter_local_notifications.dart'
as _i163;
import 'package:get_it/get_it.dart' as _i174;
import 'package:injectable/injectable.dart' as _i526;
import 'package:package_info_plus/package_info_plus.dart' as _i655;
import 'package:shared_preferences/shared_preferences.dart' as _i460;
const String _dev = 'dev';
@@ -158,23 +171,39 @@ extension GetItInjectableX on _i174.GetIt {
_i526.EnvironmentFilter? environmentFilter,
}) async {
final gh = _i526.GetItHelper(this, environment, environmentFilter);
final firebaseDi = _$FirebaseDi();
final sharedPreferencesDi = _$SharedPreferencesDi();
final databaseDi = _$DatabaseDi();
final dioDi = _$DioDi();
final autoRouteDi = _$AutoRouteDi();
final connectivityDi = _$ConnectivityDi();
final dioDi = _$DioDi();
gh.factory<_i13.CheckoutFormBloc>(() => _i13.CheckoutFormBloc());
gh.factory<_i96.PrinterBloc>(() => _i96.PrinterBloc());
gh.factory<_i257.ReportBloc>(() => _i257.ReportBloc());
gh.factory<_i334.SplitBillFormBloc>(() => _i334.SplitBillFormBloc());
await gh.factoryAsync<_i655.PackageInfo>(
() => firebaseDi.packageInfo,
preResolve: true,
);
await gh.factoryAsync<_i460.SharedPreferences>(
() => sharedPreferencesDi.prefs,
preResolve: true,
);
gh.factory<_i334.SplitBillFormBloc>(() => _i334.SplitBillFormBloc());
gh.factory<_i96.PrinterBloc>(() => _i96.PrinterBloc());
gh.factory<_i13.CheckoutFormBloc>(() => _i13.CheckoutFormBloc());
gh.factory<_i257.ReportBloc>(() => _i257.ReportBloc());
gh.singleton<_i487.DatabaseHelper>(() => databaseDi.databaseHelper);
gh.lazySingleton<_i361.Dio>(() => dioDi.dio);
gh.lazySingleton<_i800.AppRouter>(() => autoRouteDi.appRouter);
gh.lazySingleton<_i895.Connectivity>(() => connectivityDi.connectivity);
gh.lazySingleton<_i361.Dio>(() => dioDi.dio);
gh.lazySingleton<_i892.FirebaseMessaging>(() => firebaseDi.messaging);
gh.lazySingleton<_i163.FlutterLocalNotificationsPlugin>(
() => firebaseDi.localNotifications,
);
gh.lazySingleton<_i833.DeviceInfoPlugin>(() => firebaseDi.deviceInfo);
gh.lazySingleton<_i288.DeviceInfoService>(
() => _i288.DeviceInfoService(
gh<_i833.DeviceInfoPlugin>(),
gh<_i655.PackageInfo>(),
),
);
gh.lazySingleton<_i171.NetworkClient>(
() => _i171.NetworkClient(gh<_i895.Connectivity>()),
);
@@ -188,6 +217,12 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i464.ProductLocalDataProvider>(
() => _i464.ProductLocalDataProvider(gh<_i487.DatabaseHelper>()),
);
gh.lazySingleton<_i312.FcmService>(
() => _i312.FcmService(
gh<_i892.FirebaseMessaging>(),
gh<_i163.FlutterLocalNotificationsPlugin>(),
),
);
gh.factory<_i204.AuthLocalDataProvider>(
() => _i204.AuthLocalDataProvider(gh<_i460.SharedPreferences>()),
);
@@ -205,26 +240,35 @@ extension GetItInjectableX on _i174.GetIt {
gh<_i204.AuthLocalDataProvider>(),
),
);
gh.factory<_i903.BluetoothLoaderBloc>(
() => _i903.BluetoothLoaderBloc(gh<_i104.IPrinterRepository>()),
);
gh.factory<_i489.BluetoothConnectBloc>(
() => _i489.BluetoothConnectBloc(gh<_i104.IPrinterRepository>()),
);
gh.factory<_i903.BluetoothLoaderBloc>(
() => _i903.BluetoothLoaderBloc(gh<_i104.IPrinterRepository>()),
);
gh.factory<_i1028.PrinterLoaderBloc>(
() => _i1028.PrinterLoaderBloc(gh<_i104.IPrinterRepository>()),
);
gh.factory<_i21.PrintStruckBloc>(
() => _i21.PrintStruckBloc(gh<_i104.IPrinterRepository>()),
);
gh.factory<_i360.OrderRemoteDataProvider>(
() => _i360.OrderRemoteDataProvider(gh<_i457.ApiClient>()),
gh.factory<_i708.AnalyticRemoteDataProvider>(
() => _i708.AnalyticRemoteDataProvider(gh<_i457.ApiClient>()),
);
gh.factory<_i370.AuthRemoteDataProvider>(
() => _i370.AuthRemoteDataProvider(gh<_i457.ApiClient>()),
);
gh.factory<_i856.CategoryRemoteDataProvider>(
() => _i856.CategoryRemoteDataProvider(gh<_i457.ApiClient>()),
);
gh.factory<_i370.AuthRemoteDataProvider>(
() => _i370.AuthRemoteDataProvider(gh<_i457.ApiClient>()),
gh.factory<_i841.CustomerRemoteDataProvider>(
() => _i841.CustomerRemoteDataProvider(gh<_i457.ApiClient>()),
);
gh.factory<_i360.OrderRemoteDataProvider>(
() => _i360.OrderRemoteDataProvider(gh<_i457.ApiClient>()),
);
gh.factory<_i132.OutletRemoteDataProvider>(
() => _i132.OutletRemoteDataProvider(gh<_i457.ApiClient>()),
);
gh.factory<_i833.PaymentMethodRemoteDataProvider>(
() => _i833.PaymentMethodRemoteDataProvider(gh<_i457.ApiClient>()),
@@ -232,18 +276,9 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i707.ProductRemoteDataProvider>(
() => _i707.ProductRemoteDataProvider(gh<_i457.ApiClient>()),
);
gh.factory<_i132.OutletRemoteDataProvider>(
() => _i132.OutletRemoteDataProvider(gh<_i457.ApiClient>()),
);
gh.factory<_i95.TableRemoteDataProvider>(
() => _i95.TableRemoteDataProvider(gh<_i457.ApiClient>()),
);
gh.factory<_i708.AnalyticRemoteDataProvider>(
() => _i708.AnalyticRemoteDataProvider(gh<_i457.ApiClient>()),
);
gh.factory<_i841.CustomerRemoteDataProvider>(
() => _i841.CustomerRemoteDataProvider(gh<_i457.ApiClient>()),
);
gh.factory<_i776.IAuthRepository>(
() => _i941.AuthRepository(
gh<_i370.AuthRemoteDataProvider>(),
@@ -256,6 +291,13 @@ extension GetItInjectableX on _i174.GetIt {
gh<_i708.CategoryLocalDataProvider>(),
),
);
gh.factory<_i46.LoginFormBloc>(
() => _i46.LoginFormBloc(
gh<_i776.IAuthRepository>(),
gh<_i312.FcmService>(),
gh<_i288.DeviceInfoService>(),
),
);
gh.factory<_i983.ITableRepository>(
() => _i824.TableRepository(
gh<_i95.TableRemoteDataProvider>(),
@@ -288,8 +330,8 @@ extension GetItInjectableX on _i174.GetIt {
gh<_i833.PaymentMethodRemoteDataProvider>(),
),
);
gh.factory<_i46.LoginFormBloc>(
() => _i46.LoginFormBloc(gh<_i776.IAuthRepository>()),
gh.factory<_i641.LogoutBloc>(
() => _i641.LogoutBloc(gh<_i776.IAuthRepository>()),
);
gh.factory<_i1018.CategoryLoaderBloc>(
() => _i1018.CategoryLoaderBloc(gh<_i502.ICategoryRepository>()),
@@ -315,12 +357,12 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i194.PaymentFormBloc>(
() => _i194.PaymentFormBloc(gh<_i299.IOrderRepository>()),
);
gh.factory<_i822.VoidFormBloc>(
() => _i822.VoidFormBloc(gh<_i299.IOrderRepository>()),
);
gh.factory<_i837.RefundFormBloc>(
() => _i837.RefundFormBloc(gh<_i299.IOrderRepository>()),
);
gh.factory<_i822.VoidFormBloc>(
() => _i822.VoidFormBloc(gh<_i299.IOrderRepository>()),
);
gh.factory<_i683.CustomerLoaderBloc>(
() => _i683.CustomerLoaderBloc(gh<_i143.ICustomerRepository>()),
);
@@ -348,21 +390,10 @@ extension GetItInjectableX on _i174.GetIt {
gh<_i502.ICategoryRepository>(),
),
);
gh.factory<_i268.ProductAnalyticLoaderBloc>(
() => _i268.ProductAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()),
);
gh.factory<_i651.InventoryAnalyticLoaderBloc>(
() => _i651.InventoryAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()),
);
gh.factory<_i741.ProfitLossAnalyticLoaderBloc>(
() => _i741.ProfitLossAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()),
);
gh.factory<_i413.SalesAnalyticLoaderBloc>(
() => _i413.SalesAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()),
);
gh.factory<_i733.PaymentMethodAnalyticLoaderBloc>(
() => _i733.PaymentMethodAnalyticLoaderBloc(
gh<_i346.IAnalyticRepository>(),
gh.factory<_i729.SyncSettingBloc>(
() => _i729.SyncSettingBloc(
gh<_i44.IProductRepository>(),
gh<_i502.ICategoryRepository>(),
),
);
gh.factory<_i911.CategoryAnalyticLoaderBloc>(
@@ -371,16 +402,35 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i80.DashboardAnalyticLoaderBloc>(
() => _i80.DashboardAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()),
);
gh.factory<_i651.InventoryAnalyticLoaderBloc>(
() => _i651.InventoryAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()),
);
gh.factory<_i733.PaymentMethodAnalyticLoaderBloc>(
() => _i733.PaymentMethodAnalyticLoaderBloc(
gh<_i346.IAnalyticRepository>(),
),
);
gh.factory<_i268.ProductAnalyticLoaderBloc>(
() => _i268.ProductAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()),
);
gh.factory<_i741.ProfitLossAnalyticLoaderBloc>(
() => _i741.ProfitLossAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()),
);
gh.factory<_i413.SalesAnalyticLoaderBloc>(
() => _i413.SalesAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()),
);
return this;
}
}
class _$FirebaseDi extends _i857.FirebaseDi {}
class _$SharedPreferencesDi extends _i135.SharedPreferencesDi {}
class _$DatabaseDi extends _i209.DatabaseDi {}
class _$DioDi extends _i86.DioDi {}
class _$AutoRouteDi extends _i729.AutoRouteDi {}
class _$ConnectivityDi extends _i807.ConnectivityDi {}
class _$DioDi extends _i86.DioDi {}
+6 -4
View File
@@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:injectable/injectable.dart';
import 'common/service/fcm_service.dart';
import 'injection.dart';
import 'presentation/app_widget.dart';
@@ -43,14 +44,15 @@ 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();
runApp(const AppWidget());
},
(error, stack) {
// ✅ Ini udah bener
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
},
);
+4
View File
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../application/auth/auth_bloc.dart';
import '../application/auth/logout/logout_bloc.dart';
import '../application/category/category_loader/category_loader_bloc.dart';
import '../application/checkout/checkout_form/checkout_form_bloc.dart';
import '../application/customer/customer_loader/customer_loader_bloc.dart';
@@ -15,6 +16,7 @@ import '../application/printer/print_struck/print_struck_bloc.dart';
import '../application/printer/printer_form/printer_form_bloc.dart';
import '../application/printer/printer_loader/printer_loader_bloc.dart';
import '../application/product/product_loader/product_loader_bloc.dart';
import '../application/sync/sync_setting/sync_setting_bloc.dart';
import '../application/table/table_form/table_form_bloc.dart';
import '../application/table/table_loader/table_loader_bloc.dart';
import '../application/void/void_form/void_form_bloc.dart';
@@ -55,6 +57,8 @@ class _AppWidgetState extends State<AppWidget> {
BlocProvider(create: (context) => getIt<PrinterFormBloc>()),
BlocProvider(create: (context) => getIt<PrinterLoaderBloc>()),
BlocProvider(create: (context) => getIt<PrintStruckBloc>()),
BlocProvider(create: (context) => getIt<SyncSettingBloc>()),
BlocProvider(create: (context) => getIt<LogoutBloc>()),
],
child: MaterialApp.router(
debugShowCheckedModeBanner: false,
@@ -10,7 +10,7 @@ class AppIconButton extends StatelessWidget {
return InkWell(
onTap: onTap,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
border: Border.all(color: AppColor.border),

Some files were not shown because too many files have changed in this diff Show More