Feat: completed features porter queue and history transaction porter
This commit is contained in:
parent
0b381d2571
commit
08331d69ca
|
@ -638,7 +638,7 @@
|
||||||
"languageVersion": "3.4"
|
"languageVersion": "3.4"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"generated": "2025-04-23T15:39:37.293764Z",
|
"generated": "2025-04-27T19:47:24.712851Z",
|
||||||
"generator": "pub",
|
"generator": "pub",
|
||||||
"generatorVersion": "3.5.0",
|
"generatorVersion": "3.5.0",
|
||||||
"flutterRoot": "file:///D:/Flutter/flutter_sdk/flutter_3.24.0",
|
"flutterRoot": "file:///D:/Flutter/flutter_sdk/flutter_3.24.0",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
buildscript {
|
buildscript {
|
||||||
|
ext.kotlin_version = '2.1.0'
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
@ -6,7 +7,7 @@ buildscript {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath "com.android.tools.build:gradle:8.2.0"
|
classpath "com.android.tools.build:gradle:8.4.0"
|
||||||
classpath 'com.google.gms:google-services:4.4.2' // firebase
|
classpath 'com.google.gms:google-services:4.4.2' // firebase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
import 'dart:math' hide log;
|
||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
import 'package:e_porter/domain/repositories/porter_queue_repository.dart';
|
import 'package:e_porter/domain/repositories/porter_queue_repository.dart';
|
||||||
|
import 'package:firebase_database/firebase_database.dart';
|
||||||
|
|
||||||
import '../../domain/models/porter_queue_model.dart';
|
import '../../domain/models/porter_queue_model.dart';
|
||||||
|
|
||||||
|
@ -12,27 +14,41 @@ class PorterQueueRepositoryImpl implements PorterQueueRepository {
|
||||||
FirebaseFirestore? firestore,
|
FirebaseFirestore? firestore,
|
||||||
}) : _firestore = firestore ?? FirebaseFirestore.instance;
|
}) : _firestore = firestore ?? FirebaseFirestore.instance;
|
||||||
|
|
||||||
|
String _generateId() {
|
||||||
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||||
|
final rand = Random();
|
||||||
|
return List.generate(7, (index) => chars[rand.nextInt(chars.length)]).join();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> createPorterQueue(String userId) async {
|
Future<String> createPorterQueue(String userId, String locationPorter) async {
|
||||||
try {
|
try {
|
||||||
log('[PorterRepository] Membuat antrian porter untuk userId: $userId');
|
log('[PorterRepository] Membuat antrian porter untuk userId: $userId');
|
||||||
|
|
||||||
// Periksa apakah porter dengan userId ini sudah ada
|
|
||||||
final existingPorter = await getPorterByUserId(userId);
|
final existingPorter = await getPorterByUserId(userId);
|
||||||
if (existingPorter != null) {
|
if (existingPorter != null) {
|
||||||
log('[PorterRepository] Porter dengan userId: $userId sudah ada di antrian');
|
log('[PorterRepository] Porter dengan userId: $userId sudah ada di antrian');
|
||||||
|
|
||||||
|
if (!existingPorter.isAvailable) {
|
||||||
|
await _firestore.collection('porterOnline').doc(existingPorter.id).update({
|
||||||
|
'isAvailable': true,
|
||||||
|
'idUser': null,
|
||||||
|
'idTransaction': null,
|
||||||
|
'onlineAt': FieldValue.serverTimestamp(),
|
||||||
|
});
|
||||||
|
log('[PorterRepository] Status porter diperbarui menjadi available');
|
||||||
|
}
|
||||||
|
|
||||||
return existingPorter.id!;
|
return existingPorter.id!;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buat data porter baru
|
|
||||||
final now = DateTime.now();
|
|
||||||
final porterData = PorterQueueModel(
|
final porterData = PorterQueueModel(
|
||||||
userId: userId,
|
userId: userId,
|
||||||
isTaken: true,
|
isAvailable: true,
|
||||||
onlineAt: now,
|
onlineAt: DateTime.now(),
|
||||||
|
locationPorter: locationPorter,
|
||||||
).toJson();
|
).toJson();
|
||||||
|
|
||||||
// Simpan ke Firestore
|
|
||||||
final docRef = await _firestore.collection('porterOnline').add(porterData);
|
final docRef = await _firestore.collection('porterOnline').add(porterData);
|
||||||
|
|
||||||
log('[PorterRepository] Berhasil membuat antrian porter dengan ID: ${docRef.id}');
|
log('[PorterRepository] Berhasil membuat antrian porter dengan ID: ${docRef.id}');
|
||||||
|
@ -63,6 +79,16 @@ class PorterQueueRepositoryImpl implements PorterQueueRepository {
|
||||||
try {
|
try {
|
||||||
log('[PorterRepository] Menghapus antrian porter dengan ID: $porterId');
|
log('[PorterRepository] Menghapus antrian porter dengan ID: $porterId');
|
||||||
|
|
||||||
|
// Periksa apakah porter sedang memiliki transaksi aktif
|
||||||
|
final porterDoc = await _firestore.collection('porterOnline').doc(porterId).get();
|
||||||
|
if (porterDoc.exists) {
|
||||||
|
final data = porterDoc.data();
|
||||||
|
if (data != null && data['idTransaction'] != null) {
|
||||||
|
log('[PorterRepository] Porter memiliki transaksi aktif, tidak dapat dihapus');
|
||||||
|
throw Exception('Porter sedang menangani transaksi, selesaikan transaksi terlebih dahulu');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await _firestore.collection('porterOnline').doc(porterId).delete();
|
await _firestore.collection('porterOnline').doc(porterId).delete();
|
||||||
|
|
||||||
log('[PorterRepository] Berhasil menghapus antrian porter dengan ID: $porterId');
|
log('[PorterRepository] Berhasil menghapus antrian porter dengan ID: $porterId');
|
||||||
|
@ -71,4 +97,363 @@ class PorterQueueRepositoryImpl implements PorterQueueRepository {
|
||||||
throw Exception('Gagal menghapus antrian porter: $e');
|
throw Exception('Gagal menghapus antrian porter: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<PorterQueueModel?> getNextAvailablePorter() async {
|
||||||
|
try {
|
||||||
|
log('[PorterRepository] Mencari porter yang tersedia...');
|
||||||
|
|
||||||
|
final snapshot = await _firestore
|
||||||
|
.collection('porterOnline')
|
||||||
|
.where('isAvailable', isEqualTo: true)
|
||||||
|
.orderBy('onlineAt', descending: false)
|
||||||
|
.limit(1)
|
||||||
|
.get()
|
||||||
|
.timeout(Duration(seconds: 10), onTimeout: () {
|
||||||
|
throw TimeoutException('Waktu pencarian porter habis');
|
||||||
|
});
|
||||||
|
|
||||||
|
log('[PorterRepository] Query result: ${snapshot.docs.length} porters available');
|
||||||
|
|
||||||
|
if (snapshot.docs.isNotEmpty) {
|
||||||
|
final porter = PorterQueueModel.fromJson(snapshot.docs.first.data(), docId: snapshot.docs.first.id);
|
||||||
|
log('[PorterRepository] Porter tersedia: ${porter.id}');
|
||||||
|
return porter;
|
||||||
|
}
|
||||||
|
log('[PorterRepository] Tidak ada porter yang tersedia');
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
log('[PorterRepository] Error mendapatkan porter tersedia: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> assignPorterToUser(String porterId, String userId, String transactionId) async {
|
||||||
|
try {
|
||||||
|
return await _firestore.runTransaction((transaction) async {
|
||||||
|
final porterDoc = await transaction.get(_firestore.collection('porterOnline').doc(porterId));
|
||||||
|
|
||||||
|
if (!porterDoc.exists) {
|
||||||
|
log('[PorterRepository] Porter tidak ditemukan');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final porterData = porterDoc.data();
|
||||||
|
if (porterData == null || porterData['isAvailable'] != true) {
|
||||||
|
log('[PorterRepository] Porter tidak tersedia');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.update(_firestore.collection('porterOnline').doc(porterId), {
|
||||||
|
'isAvailable': false,
|
||||||
|
'idUser': userId,
|
||||||
|
'idTransaction': transactionId,
|
||||||
|
});
|
||||||
|
|
||||||
|
log('[PorterRepository] Porter berhasil ditugaskan ke user: $userId');
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log('[PorterRepository] Error menugaskan porter: $e');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> createPorterTransaction(
|
||||||
|
{required String porterTransactionId,
|
||||||
|
required String porterId,
|
||||||
|
required String passengerId,
|
||||||
|
required String ticketId,
|
||||||
|
required String transactionId,
|
||||||
|
required String locationPassenger,
|
||||||
|
required String locationPorter}) async {
|
||||||
|
try {
|
||||||
|
log('[PorterRepository] Membuat transaksi porter: $porterTransactionId');
|
||||||
|
|
||||||
|
final now = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
final kodePorter = _generateId();
|
||||||
|
|
||||||
|
// Dapatkan userId porter dari dokumen porterOnline
|
||||||
|
final porterDoc = await _firestore.collection('porterOnline').doc(porterId).get();
|
||||||
|
String porterUserId = '';
|
||||||
|
|
||||||
|
if (porterDoc.exists && porterDoc.data() != null) {
|
||||||
|
porterUserId = porterDoc.data()!['userId'] ?? '';
|
||||||
|
log('[PorterRepository] Mendapatkan userId porter: $porterUserId');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (porterUserId.isEmpty) {
|
||||||
|
log('[PorterRepository] PERINGATAN: userId porter tidak ditemukan, menggunakan porterId: $porterId');
|
||||||
|
porterUserId = porterId; // Fallback ke porterId jika userId tidak ditemukan
|
||||||
|
}
|
||||||
|
|
||||||
|
final data = {
|
||||||
|
'kodePorter': kodePorter,
|
||||||
|
'porterOnlineId': porterId,
|
||||||
|
'porterUserId': porterUserId, // Tambahkan porterUserId dalam data transaksi
|
||||||
|
'idPassenger': passengerId,
|
||||||
|
'ticketId': ticketId,
|
||||||
|
'transactionId': transactionId,
|
||||||
|
'status': 'pending',
|
||||||
|
'locationPassenger': locationPassenger,
|
||||||
|
'locationPorter': locationPorter,
|
||||||
|
'createdAt': now,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 1. Update Firestore
|
||||||
|
final batch = _firestore.batch();
|
||||||
|
final porterDocId = _firestore.collection('porterOnline').doc(porterId);
|
||||||
|
final txDoc = _firestore.collection('porterTransactions').doc(porterTransactionId);
|
||||||
|
|
||||||
|
batch.update(porterDocId, {
|
||||||
|
'isAvailable': false,
|
||||||
|
'idUser': passengerId,
|
||||||
|
'idTransaction': porterTransactionId,
|
||||||
|
'locationPorter': locationPorter,
|
||||||
|
});
|
||||||
|
batch.set(txDoc, data);
|
||||||
|
|
||||||
|
await batch.commit();
|
||||||
|
log('[PorterRepository] Dokumen Firestore berhasil diperbarui');
|
||||||
|
|
||||||
|
// 2. Simpan transaksi di Realtime Database
|
||||||
|
final rtdb = FirebaseDatabase.instance.ref();
|
||||||
|
|
||||||
|
// 2.1 Data transaksi utama
|
||||||
|
await rtdb.child('porterTransactions/$porterTransactionId').set(data);
|
||||||
|
log('[PorterRepository] Data transaksi utama disimpan di RTDB');
|
||||||
|
|
||||||
|
// 2.2 Simpan referensi di porterHistory berdasarkan porterId (untuk kompatibilitas mundur)
|
||||||
|
await rtdb.child('porterHistory/$porterId/$porterTransactionId').set({
|
||||||
|
'timestamp': now,
|
||||||
|
'transactionId': porterTransactionId,
|
||||||
|
});
|
||||||
|
log('[PorterRepository] Referensi disimpan di porterHistory/$porterId');
|
||||||
|
|
||||||
|
// 2.3 Simpan referensi di porterHistory berdasarkan userId porter (baru)
|
||||||
|
if (porterUserId != porterId && porterUserId.isNotEmpty) {
|
||||||
|
await rtdb.child('porterHistory/$porterUserId/$porterTransactionId').set({
|
||||||
|
'timestamp': now,
|
||||||
|
'transactionId': porterTransactionId,
|
||||||
|
});
|
||||||
|
log('[PorterRepository] Referensi disimpan di porterHistory/$porterUserId');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.4 Simpan referensi di passengerHistory
|
||||||
|
await rtdb.child('passengerHistory/$passengerId/$porterTransactionId').set({
|
||||||
|
'timestamp': now,
|
||||||
|
'transactionId': porterTransactionId,
|
||||||
|
});
|
||||||
|
log('[PorterRepository] Referensi disimpan di passengerHistory/$passengerId');
|
||||||
|
|
||||||
|
log('[PorterRepository] Transaksi porter berhasil dibuat');
|
||||||
|
} catch (e) {
|
||||||
|
log('[PorterRepository] Error membuat transaksi porter: $e');
|
||||||
|
throw Exception('Gagal membuat transaksi porter: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// Future<List<String>> getPorterTransactionIds(String porterId) async {
|
||||||
|
// try {
|
||||||
|
// final rtdb = FirebaseDatabase.instance.ref();
|
||||||
|
// final snapshot = await rtdb.child('porterHistory/$porterId').get();
|
||||||
|
|
||||||
|
// if (snapshot.exists && snapshot.value != null) {
|
||||||
|
// final Map<dynamic, dynamic> data = snapshot.value as Map<dynamic, dynamic>;
|
||||||
|
// return data.keys.map((key) => key.toString()).toList();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return [];
|
||||||
|
// } catch (e) {
|
||||||
|
// log('[PorterRepository] Error getting porter transaction IDs: $e');
|
||||||
|
// return [];
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// Future<Map<String, dynamic>?> getPorterTransactionById(String transactionId) async {
|
||||||
|
// try {
|
||||||
|
// final rtdb = FirebaseDatabase.instance.ref();
|
||||||
|
// final snapshot = await rtdb.child('porterTransactions/$transactionId').get();
|
||||||
|
|
||||||
|
// if (snapshot.exists && snapshot.value != null) {
|
||||||
|
// return Map<String, dynamic>.from(snapshot.value as Map);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return null;
|
||||||
|
// } catch (e) {
|
||||||
|
// log('[PorterRepository] Error getting porter transaction: $e');
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> completePorterAssignment(String porterId) async {
|
||||||
|
try {
|
||||||
|
// Dapatkan dokumen porter
|
||||||
|
final porterDoc = await _firestore.collection('porterOnline').doc(porterId).get();
|
||||||
|
if (porterDoc.exists) {
|
||||||
|
final porterData = porterDoc.data();
|
||||||
|
final transactionId = porterData?['idTransaction'];
|
||||||
|
|
||||||
|
if (transactionId != null) {
|
||||||
|
// Update status transaksi di Firestore
|
||||||
|
await _firestore.collection('porterTransactions').doc(transactionId).update({
|
||||||
|
'status': 'selesai',
|
||||||
|
'updatedAt': FieldValue.serverTimestamp(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update status transaksi di Realtime DB
|
||||||
|
final rtdb = FirebaseDatabase.instance.ref();
|
||||||
|
await rtdb.child('porterTransactions/$transactionId').update({
|
||||||
|
'status': 'selesai',
|
||||||
|
'updatedAt': DateTime.now().millisecondsSinceEpoch,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset status porter
|
||||||
|
await _firestore.collection('porterOnline').doc(porterId).update({
|
||||||
|
'isAvailable': true,
|
||||||
|
'idUser': null,
|
||||||
|
'idTransaction': null,
|
||||||
|
});
|
||||||
|
|
||||||
|
log('[PorterRepository] Tugas porter selesai, status diperbarui');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log('[PorterRepository] Error menyelesaikan tugas porter: $e');
|
||||||
|
throw Exception('Gagal menyelesaikan tugas porter: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// Future<void> createPorterTransaction(
|
||||||
|
// {required String porterTransactionId,
|
||||||
|
// required String porterId,
|
||||||
|
// required String passengerId,
|
||||||
|
// required String ticketId,
|
||||||
|
// required String transactionId,
|
||||||
|
// required String locationPassenger,
|
||||||
|
// required String locationPorter}) async {
|
||||||
|
// final now = FieldValue.serverTimestamp();
|
||||||
|
// final kodePorter = _generateId();
|
||||||
|
|
||||||
|
// final data = {
|
||||||
|
// 'kodePorter': kodePorter,
|
||||||
|
// 'porterOnlineId': porterId,
|
||||||
|
// 'idPassenger': passengerId,
|
||||||
|
// 'ticketId': ticketId,
|
||||||
|
// 'transactionId': transactionId,
|
||||||
|
// 'status': 'pending',
|
||||||
|
// 'locationPassenger': locationPassenger,
|
||||||
|
// 'locationPorter': locationPorter,
|
||||||
|
// 'createdAt': now,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// final batch = _firestore.batch();
|
||||||
|
// final porterDoc = _firestore.collection('porterOnline').doc(porterId);
|
||||||
|
// final txDoc = _firestore.collection('porterTransactions').doc(porterTransactionId);
|
||||||
|
|
||||||
|
// batch.update(porterDoc, {
|
||||||
|
// 'isAvailable': false,
|
||||||
|
// 'idUser': passengerId,
|
||||||
|
// 'idTransaction': porterTransactionId,
|
||||||
|
// 'locationPorter': locationPorter,
|
||||||
|
// });
|
||||||
|
// batch.set(txDoc, data);
|
||||||
|
|
||||||
|
// await batch.commit();
|
||||||
|
|
||||||
|
// // tulis juga ke Realtime Database agar porter bisa listen secara real-time
|
||||||
|
// final rtdb = FirebaseDatabase.instance.ref();
|
||||||
|
// await rtdb.child('porterTransactions/$porterId/$porterTransactionId').set({
|
||||||
|
// ...data,
|
||||||
|
// 'createdAt': DateTime.now().millisecondsSinceEpoch,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// Future<void> completePorterAssignment(String porterId) async {
|
||||||
|
// try {
|
||||||
|
// await _firestore.collection('porterOnline').doc(porterId).update({
|
||||||
|
// 'isAvailable': true,
|
||||||
|
// 'idUser': null,
|
||||||
|
// 'idTransaction': null,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// log('[PorterRepository] Tugas porter selesai, status diperbarui');
|
||||||
|
// } catch (e) {
|
||||||
|
// log('[PorterRepository] Error menyelesaikan tugas porter: $e');
|
||||||
|
// throw Exception('Gagal menyelesaikan tugas porter: $e');
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<PorterQueueModel?> getPorterById(String porterId) async {
|
||||||
|
try {
|
||||||
|
log('[PorterRepository] Mendapatkan porter by ID: $porterId');
|
||||||
|
|
||||||
|
final snapshot = await _firestore.collection('porterOnline').doc(porterId).get();
|
||||||
|
|
||||||
|
if (snapshot.exists && snapshot.data() != null) {
|
||||||
|
return PorterQueueModel.fromJson(snapshot.data()!, docId: snapshot.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
log('[PorterRepository] Porter tidak ditemukan dengan ID: $porterId');
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
log('[PorterRepository] Error mendapatkan porter by ID: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> checkConditionForPorter(String porterId) async {
|
||||||
|
try {
|
||||||
|
// Memeriksa apakah porter ada dan tidak memiliki transaksi aktif
|
||||||
|
final porterDoc = await _firestore.collection('porterOnline').doc(porterId).get();
|
||||||
|
|
||||||
|
if (!porterDoc.exists) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final data = porterDoc.data();
|
||||||
|
if (data != null && data['idTransaction'] != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Gagal memeriksa kondisi porter: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<PorterQueueModel?> watchPorterByUserId(String userId) {
|
||||||
|
try {
|
||||||
|
log('[PorterRepository] Memulai stream porter dengan userId: $userId');
|
||||||
|
|
||||||
|
return _firestore
|
||||||
|
.collection('porterOnline')
|
||||||
|
.where('userId', isEqualTo: userId)
|
||||||
|
.limit(1)
|
||||||
|
.snapshots()
|
||||||
|
.map((snapshot) {
|
||||||
|
if (snapshot.docs.isEmpty) {
|
||||||
|
log('[PorterRepository] Tidak ada porter dengan userId: $userId');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final doc = snapshot.docs.first;
|
||||||
|
log('[PorterRepository] Porter ditemukan dengan ID: ${doc.id}');
|
||||||
|
return PorterQueueModel.fromJson(doc.data(), docId: doc.id);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log('[PorterRepository] Error watching porter by userId: $e');
|
||||||
|
throw Exception('Gagal memantau data porter: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,32 +3,44 @@ import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
class PorterQueueModel {
|
class PorterQueueModel {
|
||||||
final String? id;
|
final String? id;
|
||||||
final String userId;
|
final String userId;
|
||||||
final bool isTaken;
|
final bool isAvailable;
|
||||||
final DateTime onlineAt;
|
final DateTime onlineAt;
|
||||||
|
final String? idUser;
|
||||||
|
final String? idTransaction;
|
||||||
|
final String? locationPorter;
|
||||||
|
|
||||||
PorterQueueModel({
|
PorterQueueModel({
|
||||||
this.id,
|
this.id,
|
||||||
required this.userId,
|
required this.userId,
|
||||||
required this.isTaken,
|
required this.isAvailable,
|
||||||
required this.onlineAt,
|
required this.onlineAt,
|
||||||
|
this.idUser,
|
||||||
|
this.idTransaction,
|
||||||
|
this.locationPorter,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory PorterQueueModel.fromJson(Map<String, dynamic> json, {String? docId}) {
|
factory PorterQueueModel.fromJson(Map<String, dynamic> json, {String? docId}) {
|
||||||
return PorterQueueModel(
|
return PorterQueueModel(
|
||||||
id: docId ?? json['id'],
|
id: docId ?? json['id'],
|
||||||
userId: json['userId'] ?? '',
|
userId: json['userId'] ?? '',
|
||||||
isTaken: json['isTaken'] ?? false,
|
isAvailable: json['isAvailable'] ?? true,
|
||||||
onlineAt: (json['onlineAt'] is Timestamp)
|
onlineAt: (json['onlineAt'] is Timestamp)
|
||||||
? (json['onlineAt'] as Timestamp).toDate()
|
? (json['onlineAt'] as Timestamp).toDate()
|
||||||
: DateTime.fromMillisecondsSinceEpoch(json['onlineAt'] ?? 0),
|
: DateTime.fromMillisecondsSinceEpoch(json['onlineAt'] ?? 0),
|
||||||
|
idUser: json['idUser'],
|
||||||
|
idTransaction: json['idTransaction'],
|
||||||
|
locationPorter: json['locationPorter'],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return {
|
return {
|
||||||
'userId': userId,
|
'userId': userId,
|
||||||
'isTaken': isTaken,
|
'isAvailable': isAvailable,
|
||||||
'onlineAt': onlineAt,
|
'onlineAt': onlineAt,
|
||||||
|
'idUser': idUser,
|
||||||
|
'idTransaction': idTransaction,
|
||||||
|
'locationPorter': locationPorter,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,27 @@
|
||||||
import '../models/porter_queue_model.dart';
|
import '../models/porter_queue_model.dart';
|
||||||
|
|
||||||
abstract class PorterQueueRepository {
|
abstract class PorterQueueRepository {
|
||||||
Future<String> createPorterQueue(String userId);
|
Future<String> createPorterQueue(String userId, String locationPorter);
|
||||||
Future<PorterQueueModel?> getPorterByUserId(String userId);
|
Future<PorterQueueModel?> getPorterByUserId(String userId);
|
||||||
Future<void> deletePorterQueue(String porterId);
|
Future<void> deletePorterQueue(String porterId);
|
||||||
}
|
|
||||||
|
// Sisi Porter
|
||||||
|
Future<PorterQueueModel?> getNextAvailablePorter();
|
||||||
|
Future<bool> assignPorterToUser(String porterId, String userId, String transactionId);
|
||||||
|
Future<void> createPorterTransaction(
|
||||||
|
{required String porterTransactionId,
|
||||||
|
required String porterId,
|
||||||
|
required String passengerId,
|
||||||
|
required String transactionId,
|
||||||
|
required String ticketId,
|
||||||
|
required String locationPassenger,
|
||||||
|
required String locationPorter});
|
||||||
|
|
||||||
|
// Future<List<String>> getPorterTransactionIds(String porterId);
|
||||||
|
// Future<Map<String, dynamic>?> getPorterTransactionById(String transactionId);
|
||||||
|
|
||||||
|
Future<void> completePorterAssignment(String porterId);
|
||||||
|
Future<PorterQueueModel?> getPorterById(String porterId);
|
||||||
|
Future<bool> checkConditionForPorter(String porterId);
|
||||||
|
Stream<PorterQueueModel?> watchPorterByUserId(String userId);
|
||||||
|
}
|
||||||
|
|
|
@ -6,8 +6,8 @@ class PorterQueueUsecase {
|
||||||
|
|
||||||
PorterQueueUsecase(this._repository);
|
PorterQueueUsecase(this._repository);
|
||||||
|
|
||||||
Future<String> createPorterQueue(String userId) {
|
Future<String> createPorterQueue(String userId, String locationPorter) {
|
||||||
return _repository.createPorterQueue(userId);
|
return _repository.createPorterQueue(userId, locationPorter);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<PorterQueueModel?> getPorterByUserId(String userId) {
|
Future<PorterQueueModel?> getPorterByUserId(String userId) {
|
||||||
|
@ -17,4 +17,61 @@ class PorterQueueUsecase {
|
||||||
Future<void> deletePorterQueue(String porterId) {
|
Future<void> deletePorterQueue(String porterId) {
|
||||||
return _repository.deletePorterQueue(porterId);
|
return _repository.deletePorterQueue(porterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sisi Porter
|
||||||
|
Future<PorterQueueModel?> getNextAvailablePorter() {
|
||||||
|
return _repository.getNextAvailablePorter();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> assignPorterToUser(String porterId, String userId, String transactionId) {
|
||||||
|
return _repository.assignPorterToUser(porterId, userId, transactionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, String>> requestPorter({
|
||||||
|
required String passengerId,
|
||||||
|
required String ticketId,
|
||||||
|
required String transactionId,
|
||||||
|
required String locationPassenger,
|
||||||
|
}) async {
|
||||||
|
final porter = await _repository.getNextAvailablePorter();
|
||||||
|
if (porter == null) throw Exception('Porter tidak tersedia');
|
||||||
|
|
||||||
|
final porterId = porter.id!;
|
||||||
|
final txId = DateTime.now().millisecondsSinceEpoch.toString();
|
||||||
|
final locPorter = porter.locationPorter!;
|
||||||
|
|
||||||
|
final ok = await _repository.assignPorterToUser(porterId, passengerId, txId);
|
||||||
|
if (!ok) throw Exception('Gagal menugaskan porter');
|
||||||
|
|
||||||
|
await _repository.createPorterTransaction(
|
||||||
|
porterTransactionId: txId,
|
||||||
|
porterId: porterId,
|
||||||
|
passengerId: passengerId,
|
||||||
|
transactionId: transactionId,
|
||||||
|
ticketId: ticketId,
|
||||||
|
locationPassenger: locationPassenger,
|
||||||
|
locationPorter: locPorter,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
'porterId': porterId,
|
||||||
|
'transactionId': txId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> completePorterAssignment(String porterId) {
|
||||||
|
return _repository.completePorterAssignment(porterId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<PorterQueueModel?> getPorterById(String porterId) {
|
||||||
|
return _repository.getPorterById(porterId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> checkConditionForPorter(String porterId) {
|
||||||
|
return _repository.checkConditionForPorter(porterId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<PorterQueueModel?> watchPorterByUserId(String userId) {
|
||||||
|
return _repository.watchPorterByUserId(userId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:e_porter/_core/utils/snackbar/snackbar_helper.dart';
|
||||||
import 'package:e_porter/domain/models/porter_queue_model.dart';
|
import 'package:e_porter/domain/models/porter_queue_model.dart';
|
||||||
import 'package:e_porter/domain/usecases/porter_queue_usecase.dart';
|
import 'package:e_porter/domain/usecases/porter_queue_usecase.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
@ -11,12 +14,12 @@ class PorterQueueController {
|
||||||
final RxString error = ''.obs;
|
final RxString error = ''.obs;
|
||||||
final Rx<PorterQueueModel?> currentPorter = Rx<PorterQueueModel?>(null);
|
final Rx<PorterQueueModel?> currentPorter = Rx<PorterQueueModel?>(null);
|
||||||
|
|
||||||
Future<String> createPorterQueue(String userId) async {
|
Future<String> createPorterQueue(String userId, String locationPorter) async {
|
||||||
try {
|
try {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
error.value = '';
|
error.value = '';
|
||||||
|
|
||||||
final porterId = await _porterQueueUsecase.createPorterQueue(userId);
|
final porterId = await _porterQueueUsecase.createPorterQueue(userId, locationPorter);
|
||||||
|
|
||||||
final porter = await _porterQueueUsecase.getPorterByUserId(userId);
|
final porter = await _porterQueueUsecase.getPorterByUserId(userId);
|
||||||
currentPorter.value = porter;
|
currentPorter.value = porter;
|
||||||
|
@ -32,7 +35,16 @@ class PorterQueueController {
|
||||||
|
|
||||||
bool validatePorterQueueForDeletion() {
|
bool validatePorterQueueForDeletion() {
|
||||||
final porter = currentPorter.value;
|
final porter = currentPorter.value;
|
||||||
return porter != null && porter.id != null;
|
if (porter == null || porter.id == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (porter.idTransaction != null) {
|
||||||
|
error.value = 'Porter sedang menangani transaksi, selesaikan transaksi terlebih dahulu';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> deletePorterQueue(String porterId) async {
|
Future<bool> deletePorterQueue(String porterId) async {
|
||||||
|
@ -40,13 +52,23 @@ class PorterQueueController {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
error.value = '';
|
error.value = '';
|
||||||
|
|
||||||
|
// Gunakan metode cek kondisi yang telah diperbaiki
|
||||||
|
final canProceed = await checkConditionForPorter(porterId);
|
||||||
|
if (!canProceed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
await _porterQueueUsecase.deletePorterQueue(porterId);
|
await _porterQueueUsecase.deletePorterQueue(porterId);
|
||||||
currentPorter.value = null;
|
currentPorter.value = null;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error.value = 'Gagal menghapus antrian porter: $e';
|
error.value = 'Gagal menghapus antrian porter: $e';
|
||||||
return false;
|
SnackbarHelper.showError(
|
||||||
|
'Gagal',
|
||||||
|
'Gagal menghapus antrian porter.',
|
||||||
|
);
|
||||||
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
|
@ -73,4 +95,140 @@ class PorterQueueController {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Belum
|
||||||
|
|
||||||
|
Future<PorterQueueModel?> getNextAvailablePorter() async {
|
||||||
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
|
error.value = '';
|
||||||
|
|
||||||
|
return await _porterQueueUsecase.getNextAvailablePorter();
|
||||||
|
} catch (e) {
|
||||||
|
error.value = 'Gagal mendapatkan porter tersedia: $e';
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> assignPorterToUser(String porterId, String userId, String transactionId) async {
|
||||||
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
|
error.value = '';
|
||||||
|
|
||||||
|
return await _porterQueueUsecase.assignPorterToUser(porterId, userId, transactionId);
|
||||||
|
} catch (e) {
|
||||||
|
error.value = 'Gagal menugaskan porter: $e';
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, String>> requestPorter({
|
||||||
|
required String passengerId,
|
||||||
|
required String ticketId,
|
||||||
|
required String transactionId,
|
||||||
|
required String location,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
|
error.value = '';
|
||||||
|
|
||||||
|
// Panggil usecase dan dapatkan result berupa map
|
||||||
|
final result = await _porterQueueUsecase.requestPorter(
|
||||||
|
passengerId: passengerId,
|
||||||
|
ticketId: ticketId,
|
||||||
|
transactionId: transactionId,
|
||||||
|
locationPassenger: location,
|
||||||
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
// Simpan pesan error dan lempar ulang
|
||||||
|
error.value = e.toString();
|
||||||
|
rethrow;
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> completePorterAssignment(String porterId) async {
|
||||||
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
|
error.value = '';
|
||||||
|
|
||||||
|
await _porterQueueUsecase.completePorterAssignment(porterId);
|
||||||
|
|
||||||
|
if (currentPorter.value?.id == porterId) {
|
||||||
|
final userId = currentPorter.value?.userId;
|
||||||
|
if (userId != null) {
|
||||||
|
await loadCurrentPorter(userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
error.value = 'Gagal menyelesaikan tugas porter: $e';
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> checkConditionForPorter(String porterId) async {
|
||||||
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
|
error.value = '';
|
||||||
|
|
||||||
|
// 1. Dapatkan data terbaru porter langsung dari repository
|
||||||
|
final freshPorterData = await _porterQueueUsecase.getPorterById(porterId);
|
||||||
|
|
||||||
|
if (freshPorterData == null) {
|
||||||
|
error.value = 'Data porter tidak ditemukan.';
|
||||||
|
SnackbarHelper.showError('Gagal', error.value);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Gunakan data terbaru untuk validasi
|
||||||
|
if (freshPorterData.idTransaction != null) {
|
||||||
|
error.value = 'Porter sedang menangani transaksi, selesaikan transaksi terlebih dahulu.';
|
||||||
|
SnackbarHelper.showError('Gagal', error.value);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (freshPorterData.idUser != null) {
|
||||||
|
error.value = 'Porter masih melayani penumpang, selesaikan tugas terlebih dahulu.';
|
||||||
|
SnackbarHelper.showError('Gagal', error.value);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (freshPorterData.isAvailable != true) {
|
||||||
|
error.value = 'Porter belum tersedia untuk keluar antrian.';
|
||||||
|
SnackbarHelper.showError('Gagal', error.value);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
error.value = 'Gagal memeriksa kondisi porter: $e';
|
||||||
|
SnackbarHelper.showError('Gagal', 'Gagal memeriksa kondisi porter.');
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<PorterQueueModel?> watchPorter(String userId) {
|
||||||
|
if (userId.isEmpty) {
|
||||||
|
log('[PorterQueueController] Error: userId kosong');
|
||||||
|
return Stream.value(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
log('[PorterQueueController] Memulai pemantauan porter dengan userId: $userId');
|
||||||
|
return _porterQueueUsecase.watchPorterByUserId(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setError(String message) {
|
||||||
|
error.value = message;
|
||||||
|
log('[PorterQueueController] Error: $message');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,6 +120,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Periksa apakah ada porterOnline yang aktif
|
||||||
if (_porterQueueController.currentPorter.value == null) {
|
if (_porterQueueController.currentPorter.value == null) {
|
||||||
SnackbarHelper.showError(
|
SnackbarHelper.showError(
|
||||||
'Gagal',
|
'Gagal',
|
||||||
|
@ -127,9 +128,15 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final porterId = _porterQueueController.currentPorter.value!.id!;
|
|
||||||
final canProceed = await _porterQueueController.checkConditionForPorter(porterId);
|
|
||||||
|
|
||||||
|
// Dapatkan ID porter saat ini
|
||||||
|
final porterId = _porterQueueController.currentPorter.value!.id!;
|
||||||
|
|
||||||
|
// Muat ulang data porter sebelum melakukan pengecekan (optional)
|
||||||
|
await _porterQueueController.loadCurrentPorter(validUserId);
|
||||||
|
|
||||||
|
// Gunakan metode cek kondisi yang diperbaiki
|
||||||
|
final canProceed = await _porterQueueController.checkConditionForPorter(porterId);
|
||||||
if (!canProceed) {
|
if (!canProceed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -158,7 +165,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
}
|
}
|
||||||
SnackbarHelper.showError(
|
SnackbarHelper.showError(
|
||||||
'Gagal',
|
'Gagal',
|
||||||
'Gagal menghentikan antrian porter.',
|
'Gagal menghentikan antrian porter: ${e.toString()}',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue