From c7ed45a374cf4e46c28e55dc756eeb6546270ddb Mon Sep 17 00:00:00 2001 From: Efril Date: Sun, 24 May 2026 00:59:02 +0700 Subject: [PATCH] fix printer --- README.md | 219 +++++++++++++++++- .../printer_form/printer_form_bloc.dart | 14 +- lib/env.dart | 2 +- .../printer/dtos/printer_dto.dart | 15 +- .../repositories/printer_repository.dart | 1 + .../components/print/print_ui.dart | 16 +- .../print/receipt_component_builder.dart | 5 + pubspec.yaml | 2 +- 8 files changed, 251 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index f878649..1a69632 100644 --- a/README.md +++ b/README.md @@ -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 +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 diff --git a/lib/application/printer/printer_form/printer_form_bloc.dart b/lib/application/printer/printer_form/printer_form_bloc.dart index d8e3e1d..842872c 100644 --- a/lib/application/printer/printer_form/printer_form_bloc.dart +++ b/lib/application/printer/printer_form/printer_form_bloc.dart @@ -117,6 +117,13 @@ class PrinterFormBloc extends Bloc { 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 printValue = []; if (e.code == "receipt") { @@ -124,29 +131,34 @@ class PrinterFormBloc extends Bloc { 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 { code: e.code, name: state.name, address: e.macAccdress, - paper: state.paper, + paper: paper.toString(), type: state.type, ); diff --git a/lib/env.dart b/lib/env.dart index dc5b10b..4e188b8 100644 --- a/lib/env.dart +++ b/lib/env.dart @@ -10,7 +10,7 @@ abstract class Env { @dev class DevEnv implements Env { @override - String get baseUrl => 'http://192.168.1.13:4000'; + String get baseUrl => 'https://api-pos.apskel.id'; @override String get dbName => "apskel_pos_dev.db"; // example value diff --git a/lib/infrastructure/printer/dtos/printer_dto.dart b/lib/infrastructure/printer/dtos/printer_dto.dart index 5ca4849..82ba562 100644 --- a/lib/infrastructure/printer/dtos/printer_dto.dart +++ b/lib/infrastructure/printer/dtos/printer_dto.dart @@ -15,8 +15,19 @@ class PrinterDto with _$PrinterDto { @JsonKey(name: 'updated_at') required DateTime updatedAt, }) = _PrinterDto; - factory PrinterDto.fromJson(Map json) => - _$PrinterDtoFromJson(json); + factory PrinterDto.fromJson(Map 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( diff --git a/lib/infrastructure/printer/repositories/printer_repository.dart b/lib/infrastructure/printer/repositories/printer_repository.dart index dbebbc3..c8cb3b6 100644 --- a/lib/infrastructure/printer/repositories/printer_repository.dart +++ b/lib/infrastructure/printer/repositories/printer_repository.dart @@ -674,6 +674,7 @@ class PrinterRepository implements IPrinterRepository { 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) { diff --git a/lib/presentation/components/print/print_ui.dart b/lib/presentation/components/print/print_ui.dart index f3c1d79..0aa50ed 100644 --- a/lib/presentation/components/print/print_ui.dart +++ b/lib/presentation/components/print/print_ui.dart @@ -21,7 +21,7 @@ class PrintUi { ); final builder = ReceiptComponentBuilder( generator: generator, - paperSize: 58, + paperSize: paper, ); bytes += generator.reset(); @@ -85,7 +85,7 @@ class PrintUi { ); final builder = ReceiptComponentBuilder( generator: generator, - paperSize: 58, + paperSize: paper, ); bytes += generator.reset(); @@ -147,7 +147,7 @@ class PrintUi { ); final builder = ReceiptComponentBuilder( generator: generator, - paperSize: 58, + paperSize: paper, ); for (final item in order.orderItems) { @@ -198,7 +198,7 @@ class PrintUi { ); final builder = ReceiptComponentBuilder( generator: generator, - paperSize: 58, + paperSize: paper, ); for (final item in order.orderItems) { @@ -248,7 +248,7 @@ class PrintUi { ); final builder = ReceiptComponentBuilder( generator: generator, - paperSize: 58, + paperSize: paper, ); bytes += generator.reset(); @@ -289,7 +289,7 @@ class PrintUi { ); final builder = ReceiptComponentBuilder( generator: generator, - paperSize: 58, + paperSize: paper, ); bytes += generator.reset(); @@ -355,7 +355,7 @@ class PrintUi { ); final builder = ReceiptComponentBuilder( generator: generator, - paperSize: 58, + paperSize: paper, ); bytes += generator.reset(); @@ -420,7 +420,7 @@ class PrintUi { ); final builder = ReceiptComponentBuilder( generator: generator, - paperSize: 58, + paperSize: paper, ); bytes += generator.reset(); diff --git a/lib/presentation/components/print/receipt_component_builder.dart b/lib/presentation/components/print/receipt_component_builder.dart index 9691950..5c2dd4a 100644 --- a/lib/presentation/components/print/receipt_component_builder.dart +++ b/lib/presentation/components/print/receipt_component_builder.dart @@ -1,4 +1,5 @@ import 'package:esc_pos_utils_plus/esc_pos_utils_plus.dart'; +import 'package:flutter/foundation.dart'; import 'package:intl/intl.dart'; /// Reusable component builder untuk thermal receipt printer @@ -265,7 +266,11 @@ class ReceiptComponentBuilder { height: PosTextSize.size1, width: PosTextSize.size1, ); + if (kDebugMode) { + bytes += textCenter("$paperSize MM", ); + } bytes += feed(paperSize == 80 ? 3 : 1); + bytes += generator.cut(); return bytes; diff --git a/pubspec.yaml b/pubspec.yaml index 58e97fc..2b4b88f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: "A new Flutter project." publish_to: "none" -version: 1.0.4+9 +version: 1.0.5+10 environment: sdk: ^3.8.1