fix printer

This commit is contained in:
Efril 2026-05-24 00:59:02 +07:00
parent a0610459bf
commit c7ed45a374
8 changed files with 251 additions and 23 deletions

219
README.md
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

View File

@ -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,
);

View File

@ -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

View File

@ -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(

View File

@ -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) {

View File

@ -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();

View File

@ -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;

View File

@ -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