Files
bumrent_app/lib/app/data/providers/aset_provider.dart
Andreas Malvino c4dd4fdfa2 fitur order paket
2025-06-05 17:00:44 +07:00

1239 lines
42 KiB
Dart
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import '../models/aset_model.dart';
import '../models/foto_aset_model.dart';
import '../models/satuan_waktu_model.dart';
import '../models/satuan_waktu_sewa_model.dart';
import 'package:intl/intl.dart';
class AsetProvider extends GetxService {
late final SupabaseClient client;
AsetProvider() {
client = Supabase.instance.client;
}
// Mendapatkan semua aset dengan kategori "sewa"
Future<List<AsetModel>> getSewaAsets() async {
try {
debugPrint('Fetching aset with kategori: sewa');
// Query untuk mendapatkan semua aset dengan kategori "sewa"
final response = await client
.from('aset')
.select('*')
.eq('kategori', 'sewa')
.eq('status', 'tersedia') // Hanya yang tersedia
.order('nama', ascending: true); // Urutan berdasarkan nama
debugPrint('Fetched ${response.length} aset');
// Konversi response ke list AsetModel
List<AsetModel> asets =
response.map<AsetModel>((item) => AsetModel.fromJson(item)).toList();
// Untuk setiap aset, ambil foto pertama dan satuan waktu sewa
for (var aset in asets) {
await _attachFirstPhoto(aset);
await attachSatuanWaktuSewa(aset);
}
return asets;
} catch (e) {
debugPrint('Error fetching aset: $e');
return [];
}
}
// Mendapatkan semua aset dengan kategori "langganan"
Future<List<AsetModel>> getLanggananAsets() async {
try {
debugPrint('Fetching aset with kategori: langganan');
// Query untuk mendapatkan semua aset dengan kategori "langganan"
final response = await client
.from('aset')
.select('*')
.eq('kategori', 'langganan')
.eq('status', 'tersedia') // Hanya yang tersedia
.order('nama', ascending: true); // Urutan berdasarkan nama
debugPrint('Fetched ${response.length} langganan aset');
// Konversi response ke list AsetModel
List<AsetModel> asets =
response.map<AsetModel>((item) => AsetModel.fromJson(item)).toList();
// Untuk setiap aset, ambil foto pertama dan satuan waktu sewa
for (var aset in asets) {
await _attachFirstPhoto(aset);
await attachSatuanWaktuSewa(aset);
}
return asets;
} catch (e) {
debugPrint('Error fetching langganan asets: $e');
return [];
}
}
// Mendapatkan aset berdasarkan ID
Future<AsetModel?> getAsetById(String asetId) async {
try {
debugPrint('📂 Fetching aset with ID: $asetId');
// Query untuk mendapatkan aset dengan ID tertentu
final response =
await client.from('aset').select('*').eq('id', asetId).maybeSingle();
debugPrint('📂 Raw response type: ${response.runtimeType}');
debugPrint('📂 Raw response: $response');
if (response == null) {
debugPrint('❌ Aset dengan ID $asetId tidak ditemukan');
return null;
}
debugPrint(
'✅ Successfully fetched aset with ID: $asetId, name: ${response['nama']}',
);
// Konversi response ke AsetModel
AsetModel aset = AsetModel.fromJson(response);
debugPrint('✅ AsetModel created: ${aset.id} - ${aset.nama}');
// Ambil foto dan satuan waktu sewa untuk aset ini
await _attachFirstPhoto(aset);
await attachSatuanWaktuSewa(aset);
await loadAssetPhotos(aset);
return aset;
} catch (e, stackTrace) {
debugPrint('❌ Error fetching aset by ID: $e');
debugPrint('❌ StackTrace: $stackTrace');
return null;
}
}
// Load all photos for an asset
Future<void> loadAssetPhotos(AsetModel aset) async {
try {
final photos = await getAsetPhotos(aset.id);
if (photos.isNotEmpty &&
(aset.imageUrl == null || aset.imageUrl!.isEmpty)) {
aset.imageUrl = photos.first.fotoAset;
}
} catch (e) {
debugPrint('Error loading asset photos for ID ${aset.id}: $e');
}
}
// Fungsi untuk mengambil foto pertama dari aset
Future<void> _attachFirstPhoto(AsetModel aset) async {
try {
final responsePhoto =
await client
.from('foto_aset')
.select('*')
.eq('id_aset', aset.id)
.limit(1)
.maybeSingle();
if (responsePhoto != null) {
final fotoAset = FotoAsetModel.fromJson(responsePhoto);
aset.imageUrl = fotoAset.fotoAset;
}
} catch (e) {
debugPrint('Error fetching photo for aset ${aset.id}: $e');
}
}
// Fungsi untuk mendapatkan semua foto aset berdasarkan ID aset
Future<List<FotoAsetModel>> getAsetPhotos(String asetId) async {
try {
debugPrint('Fetching photos for aset ID: $asetId');
final response = await client
.from('foto_aset')
.select('*')
.eq('id_aset', asetId)
.order('created_at');
debugPrint('Fetched ${response.length} photos for aset ID: $asetId');
// Konversi response ke list FotoAsetModel
return (response as List)
.map<FotoAsetModel>((item) => FotoAsetModel.fromJson(item))
.toList();
} catch (e) {
debugPrint('Error fetching photos for aset ID $asetId: $e');
return [];
}
}
// Retrieve bookings for a specific asset on a specific date
Future<List<Map<String, dynamic>>> getAsetBookings(
String asetId,
String date,
) async {
try {
// Convert the date to DateTime for comparison
final targetDate = DateTime.parse(date);
debugPrint('🔍 Fetching bookings for asset $asetId on date $date');
// Query booked_detail table (previously was sewa_aset table) for bookings related to this asset
final response = await client
.from('booked_detail')
.select('id, waktu_mulai, waktu_selesai, sewa_aset_id, kuantitas')
.eq('aset_id', asetId)
.order('waktu_mulai', ascending: true);
// Filter bookings to only include those that overlap with our target date
final bookingsForDate =
response.where((booking) {
if (booking['waktu_mulai'] == null ||
booking['waktu_selesai'] == null) {
debugPrint('⚠️ Booking has null timestamp: $booking');
return false;
}
// Parse the timestamps
final DateTime waktuMulai = DateTime.parse(booking['waktu_mulai']);
final DateTime waktuSelesai = DateTime.parse(
booking['waktu_selesai'],
);
// Check if booking overlaps with our target date
final bookingStartDate = DateTime(
waktuMulai.year,
waktuMulai.month,
waktuMulai.day,
);
final bookingEndDate = DateTime(
waktuSelesai.year,
waktuSelesai.month,
waktuSelesai.day,
);
final targetDateOnly = DateTime(
targetDate.year,
targetDate.month,
targetDate.day,
);
// The booking overlaps with our target date if:
// 1. The booking starts on or before our target date AND
// 2. The booking ends on or after our target date
return !bookingStartDate.isAfter(targetDateOnly) &&
!bookingEndDate.isBefore(targetDateOnly);
}).toList();
debugPrint(
'📅 Found ${bookingsForDate.length} bookings for date $date from booked_detail table',
);
// Return the complete booking information with original timestamps
return bookingsForDate.map((booking) {
// Parse the timestamps for debugging
final DateTime waktuMulai = DateTime.parse(booking['waktu_mulai']);
final DateTime waktuSelesai = DateTime.parse(booking['waktu_selesai']);
// Return the full booking data with formatted display times
return {
'id':
booking['sewa_aset_id'] ??
booking['id'], // Use sewa_aset_id as id if available
'waktu_mulai': booking['waktu_mulai'], // Keep original ISO timestamp
'waktu_selesai':
booking['waktu_selesai'], // Keep original ISO timestamp
'jam_mulai': DateFormat('HH:mm').format(waktuMulai), // For display
'jam_selesai': DateFormat(
'HH:mm',
).format(waktuSelesai), // For display
'tanggal_mulai': DateFormat(
'yyyy-MM-dd',
).format(waktuMulai), // For calculations
'tanggal_selesai': DateFormat(
'yyyy-MM-dd',
).format(waktuSelesai), // For calculations
'kuantitas':
booking['kuantitas'] ?? 1, // Default to 1 if not specified
};
}).toList();
} catch (e) {
debugPrint('❌ Error getting asset bookings: $e');
return [];
}
}
// Fungsi untuk membuat pesanan sewa aset
Future<bool> createSewaAsetOrder(Map<String, dynamic> orderData) async {
try {
debugPrint('🔄 Creating sewa_aset order with data:');
orderData.forEach((key, value) {
debugPrint(' $key: $value');
});
final response =
await client.from('sewa_aset').insert(orderData).select().single();
debugPrint('✅ Order created successfully: ${response['id']}');
return true;
} catch (e) {
debugPrint('❌ Error creating sewa_aset order: $e');
debugPrint('❌ Stack trace: ${StackTrace.current}');
// Check for specific error types
if (e.toString().contains('duplicate key')) {
debugPrint('❌ This appears to be a duplicate key error');
} else if (e.toString().contains('violates foreign key constraint')) {
debugPrint('❌ This appears to be a foreign key constraint violation');
} else if (e.toString().contains('violates not-null constraint')) {
debugPrint('❌ This appears to be a null value in a required field');
}
return false;
}
}
// Fungsi untuk membuat tagihan sewa
Future<bool> createTagihanSewa(Map<String, dynamic> tagihanData) async {
try {
debugPrint('🔄 Creating tagihan_sewa with data:');
tagihanData.forEach((key, value) {
debugPrint(' $key: $value');
});
// Ensure we don't try to insert a nama_aset field that no longer exists
if (tagihanData.containsKey('nama_aset')) {
debugPrint(
'⚠️ Removing nama_aset field from tagihan_sewa data as it does not exist in the table',
);
tagihanData.remove('nama_aset');
}
final response =
await client
.from('tagihan_sewa')
.insert(tagihanData)
.select()
.single();
debugPrint('✅ Tagihan created successfully: ${response['id']}');
return true;
} catch (e) {
debugPrint('❌ Error creating tagihan_sewa: $e');
debugPrint('❌ Stack trace: ${StackTrace.current}');
// Check for specific error types
if (e.toString().contains('duplicate key')) {
debugPrint('❌ This appears to be a duplicate key error');
} else if (e.toString().contains('violates foreign key constraint')) {
debugPrint('❌ This appears to be a foreign key constraint violation');
} else if (e.toString().contains('violates not-null constraint')) {
debugPrint('❌ This appears to be a null value in a required field');
} else if (e.toString().contains('Could not find the')) {
debugPrint(
'❌ This appears to be a column mismatch error - check field names',
);
// Print the field names from the data to help debug
debugPrint('❌ Fields in provided data: ${tagihanData.keys.toList()}');
}
return false;
}
}
// Fungsi untuk membuat booked detail
Future<bool> createBookedDetail(Map<String, dynamic> bookedDetailData) async {
try {
debugPrint('🔄 Creating booked_detail with data:');
bookedDetailData.forEach((key, value) {
debugPrint(' $key: $value');
});
// Ensure we don't try to insert a status field that no longer exists
if (bookedDetailData.containsKey('status')) {
debugPrint(
'⚠️ Removing status field from booked_detail data as it does not exist in the table',
);
bookedDetailData.remove('status');
}
final response =
await client
.from('booked_detail')
.insert(bookedDetailData)
.select()
.single();
debugPrint('✅ Booked detail created successfully: ${response['id']}');
return true;
} catch (e) {
debugPrint('❌ Error creating booked_detail: $e');
debugPrint('❌ Stack trace: ${StackTrace.current}');
// Check for specific error types
if (e.toString().contains('duplicate key')) {
debugPrint('❌ This appears to be a duplicate key error');
} else if (e.toString().contains('violates foreign key constraint')) {
debugPrint('❌ This appears to be a foreign key constraint violation');
} else if (e.toString().contains('violates not-null constraint')) {
debugPrint('❌ This appears to be a null value in a required field');
} else if (e.toString().contains('Could not find the')) {
debugPrint(
'❌ This appears to be a column mismatch error - check field names',
);
// Print the field names from the data to help debug
debugPrint(
'❌ Fields in provided data: ${bookedDetailData.keys.toList()}',
);
}
return false;
}
}
// Fungsi untuk membuat pesanan lengkap (sewa_aset, booked_detail, dan tagihan_sewa) dalam satu operasi
Future<bool> createCompleteOrder({
required Map<String, dynamic> sewaAsetData,
required Map<String, dynamic> bookedDetailData,
required Map<String, dynamic> tagihanSewaData,
}) async {
try {
debugPrint('🔄 Creating complete order with transaction');
debugPrint('📦 sewa_aset data:');
sewaAsetData.forEach((key, value) => debugPrint(' $key: $value'));
debugPrint('📦 booked_detail data:');
bookedDetailData.forEach((key, value) => debugPrint(' $key: $value'));
// Ensure we don't try to insert a status field that no longer exists
if (bookedDetailData.containsKey('status')) {
debugPrint(
'⚠️ Removing status field from booked_detail data as it does not exist in the table',
);
bookedDetailData.remove('status');
}
debugPrint('📦 tagihan_sewa data:');
tagihanSewaData.forEach((key, value) => debugPrint(' $key: $value'));
// Ensure we don't try to insert a nama_aset field that no longer exists
if (tagihanSewaData.containsKey('nama_aset')) {
debugPrint(
'⚠️ Removing nama_aset field from tagihan_sewa data as it does not exist in the table',
);
tagihanSewaData.remove('nama_aset');
}
// Insert all three records
final sewaAsetResult =
await client.from('sewa_aset').insert(sewaAsetData).select().single();
debugPrint('✅ sewa_aset created: ${sewaAsetResult['id']}');
final bookedDetailResult =
await client
.from('booked_detail')
.insert(bookedDetailData)
.select()
.single();
debugPrint('✅ booked_detail created: ${bookedDetailResult['id']}');
final tagihanSewaResult =
await client
.from('tagihan_sewa')
.insert(tagihanSewaData)
.select()
.single();
debugPrint('✅ tagihan_sewa created: ${tagihanSewaResult['id']}');
debugPrint('✅ Complete order created successfully!');
return true;
} catch (e) {
debugPrint('❌ Error creating complete order: $e');
debugPrint('❌ Stack trace: ${StackTrace.current}');
// Check for specific error types
if (e.toString().contains('duplicate key')) {
debugPrint('❌ This appears to be a duplicate key error');
} else if (e.toString().contains('violates foreign key constraint')) {
debugPrint('❌ This appears to be a foreign key constraint violation');
} else if (e.toString().contains('violates not-null constraint')) {
debugPrint('❌ This appears to be a null value in a required field');
} else if (e.toString().contains('Could not find the')) {
debugPrint(
'❌ This appears to be a column mismatch error - check field names',
);
// Print the field names from each data object to help debug
debugPrint('❌ Fields in sewa_aset data: ${sewaAsetData.keys.toList()}');
debugPrint(
'❌ Fields in booked_detail data: ${bookedDetailData.keys.toList()}',
);
debugPrint(
'❌ Fields in tagihan_sewa data: ${tagihanSewaData.keys.toList()}',
);
}
return false;
}
}
// Fungsi untuk mendapatkan data satuan waktu berdasarkan ID dari tabel `satuan_waktu`
Future<SatuanWaktuModel?> getSatuanWaktuById(String id) async {
try {
// Asumsikan client adalah instance Supabase (atau library serupa)
final response =
await client
.from('satuan_waktu')
.select('*')
.eq('id', id)
.maybeSingle();
if (response == null) {
debugPrint('Tidak ditemukan data satuan waktu untuk id: $id');
return null;
}
return SatuanWaktuModel.fromJson(response);
} catch (e) {
debugPrint('Error fetching satuan waktu by id: $e');
return null;
}
}
// Fungsi untuk mendapatkan semua data satuan waktu dari tabel `satuan_waktu`
// Biasanya digunakan untuk menampilkan pilihan pada form atau filter
Future<List<SatuanWaktuModel>> getAllSatuanWaktu() async {
try {
final response = await client
.from('satuan_waktu')
.select('*')
.order('nama_satuan_waktu', ascending: true);
// Pastikan response berupa list
return (response as List)
.map<SatuanWaktuModel>((item) => SatuanWaktuModel.fromJson(item))
.toList();
} catch (e) {
debugPrint('Error fetching all satuan waktu: $e');
return [];
}
}
// Fungsi untuk mendapatkan data satuan waktu sewa untuk suatu aset tertentu
// Data diambil dari tabel `satuan_waktu_sewa` dan langsung melakukan join ke tabel `satuan_waktu`
Future<List<Map<String, dynamic>>> getSatuanWaktuSewa(String asetId) async {
try {
debugPrint('Fetching satuan waktu sewa for aset $asetId with join...');
// Query untuk mendapatkan data dari satuan_waktu_sewa dengan join ke satuan_waktu
final response = await client
.from('satuan_waktu_sewa')
.select('''
id,
aset_id,
satuan_waktu_id,
harga,
denda,
maksimal_waktu,
satuan_waktu:satuan_waktu_id(id, nama_satuan_waktu)
''')
.eq('aset_id', asetId);
debugPrint('Join query raw response type: ${response.runtimeType}');
debugPrint('Join query raw response: $response');
List<Map<String, dynamic>> result = [];
debugPrint('Response is List with ${response.length} items');
for (var item in response) {
try {
debugPrint('Processing item: $item');
// Pengecekan null dan tipe data yang lebih aman
var satuanWaktu = item['satuan_waktu'];
String namaSatuanWaktu = '';
if (satuanWaktu != null) {
if (satuanWaktu is Map) {
// Jika satuan_waktu adalah Map
namaSatuanWaktu =
satuanWaktu['nama_satuan_waktu']?.toString() ?? '';
} else if (satuanWaktu is List && satuanWaktu.isNotEmpty) {
// Jika satuan_waktu adalah List
var firstItem = satuanWaktu.first;
if (firstItem is Map) {
namaSatuanWaktu =
firstItem['nama_satuan_waktu']?.toString() ?? '';
}
}
}
final resultItem = {
'id': item['id']?.toString() ?? '',
'aset_id': item['aset_id']?.toString() ?? '',
'satuan_waktu_id': item['satuan_waktu_id']?.toString() ?? '',
'harga': item['harga'] ?? 0,
'denda': item['denda'] ?? 0,
'maksimal_waktu': item['maksimal_waktu'] ?? 0,
'nama_satuan_waktu': namaSatuanWaktu,
};
debugPrint('Successfully processed item: $resultItem');
result.add(resultItem);
} catch (e) {
debugPrint('Error processing item: $e');
debugPrint('Item data: $item');
}
}
debugPrint(
'Processed ${result.length} satuan waktu sewa records for aset $asetId',
);
return result;
} catch (e) {
debugPrint('Error fetching satuan waktu sewa for aset $asetId: $e');
debugPrint('Stack trace: ${StackTrace.current}');
return [];
}
}
// Fungsi untuk melampirkan data satuan waktu sewa ke model aset secara langsung
// Fungsi ini akan dipanggil misalnya saat Anda memuat detail aset atau list aset
Future<void> attachSatuanWaktuSewa(AsetModel aset) async {
try {
debugPrint(
'Attaching satuan waktu sewa to aset ${aset.id} (${aset.nama})',
);
// Ambil semua data satuan waktu sewa untuk aset tersebut
final satuanWaktuSewaList = await getSatuanWaktuSewa(aset.id);
// Urutkan data satuan waktu sewa, Jam dulu, kemudian Hari, kemudian lainnya
satuanWaktuSewaList.sort((a, b) {
final namaA = (a['nama_satuan_waktu'] ?? '').toString().toLowerCase();
final namaB = (b['nama_satuan_waktu'] ?? '').toString().toLowerCase();
// Jika ada 'jam', tempatkan di urutan pertama
if (namaA.contains('jam') && !namaB.contains('jam')) {
return -1;
}
// Jika ada 'hari', tempatkan di urutan kedua
else if (!namaA.contains('jam') &&
namaA.contains('hari') &&
!namaB.contains('jam') &&
!namaB.contains('hari')) {
return -1;
}
// Jika keduanya 'jam' atau keduanya 'hari' atau keduanya lainnya, pertahankan urutan asli
else if ((namaA.contains('jam') && namaB.contains('jam')) ||
(namaA.contains('hari') && namaB.contains('hari'))) {
return 0;
}
// Jika b adalah 'jam', tempatkan b lebih dulu
else if (!namaA.contains('jam') && namaB.contains('jam')) {
return 1;
}
// Jika b adalah 'hari' dan a bukan 'jam', tempatkan b lebih dulu
else if (!namaA.contains('jam') &&
!namaA.contains('hari') &&
!namaB.contains('jam') &&
namaB.contains('hari')) {
return 1;
}
// Default, pertahankan urutan
return 0;
});
debugPrint('Sorted satuan waktu sewa list: $satuanWaktuSewaList');
// Bersihkan data lama dan masukkan data baru
aset.satuanWaktuSewa.clear();
aset.satuanWaktuSewa.addAll(satuanWaktuSewaList);
// Debug: tampilkan data satuan waktu sewa yang berhasil dilampirkan
if (satuanWaktuSewaList.isNotEmpty) {
debugPrint(
'Attached ${satuanWaktuSewaList.length} satuan waktu sewa to aset ${aset.id}:',
);
for (var sws in satuanWaktuSewaList) {
debugPrint(
' - ID: ${sws['id']}, Harga: ${sws['harga']}, Satuan Waktu: ${sws['nama_satuan_waktu']} (${sws['satuan_waktu_id']})',
);
}
} else {
debugPrint('No satuan waktu sewa found for aset ${aset.id}');
}
} catch (e) {
debugPrint('Error attaching satuan waktu sewa: $e');
}
}
// Fungsi untuk memformat harga ke format rupiah (contoh: Rp 3.000)
String formatPrice(int price) {
// RegExp untuk menambahkan titik sebagai pemisah ribuan
return 'Rp ${price.toString().replaceAllMapped(RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), (Match m) => '${m[1]}.')}';
}
String _formatNumber(dynamic number) {
if (number == null) return '0';
// Pastikan angka dikonversi ke string
var numStr = number.toString();
// Tangani kasus ketika number bukan angka
try {
return numStr.replaceAllMapped(
RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'),
(Match match) => '${match[1]}.',
);
} catch (e) {
return numStr;
}
}
// Method untuk pemesanan aset
Future<bool> orderAset({
required String userId,
required String asetId,
required String satuanWaktuSewaId,
required int durasi,
required int totalHarga,
}) async {
try {
debugPrint('Creating order for aset $asetId by user $userId');
// Dapatkan tanggal hari ini
final tanggalPemesanan = DateTime.now().toIso8601String();
// Buat pesanan baru
final response =
await client
.from('pesanan')
.insert({
'user_id': userId,
'aset_id': asetId,
'satuan_waktu_sewa_id': satuanWaktuSewaId,
'tanggal_pemesanan': tanggalPemesanan,
'durasi': durasi,
'total_harga': totalHarga,
'status': 'pending', // Status awal pesanan
})
.select('id')
.single();
// Periksa apakah pesanan berhasil dibuat
if (response['id'] != null) {
debugPrint('Order created successfully with ID: ${response['id']}');
return true;
} else {
debugPrint('Failed to create order: Response is null or missing ID');
return false;
}
} catch (e) {
debugPrint('Error creating order: $e');
return false;
}
}
// Get daily bookings for an asset for a date range
Future<List<Map<String, dynamic>>> getAsetDailyBookings(
String asetId,
String startDate,
String endDate,
) async {
try {
debugPrint(
'🔍 Fetching daily bookings for asset $asetId from $startDate to $endDate from booked_detail table',
);
// Parse dates for comparison
final startDateTime = DateTime.parse(startDate);
final endDateTime = DateTime.parse(endDate);
// Query booked_detail table (previously was sewa_aset table) for daily bookings related to this asset
final response = await client
.from('booked_detail')
.select('id, waktu_mulai, waktu_selesai, sewa_aset_id, kuantitas')
.eq('aset_id', asetId)
.order('waktu_mulai', ascending: true);
// Filter bookings that overlap with the requested date range
final List<Map<String, dynamic>> bookingsInRange =
response.where((booking) {
if (booking['waktu_mulai'] == null ||
booking['waktu_selesai'] == null) {
debugPrint('⚠️ Booking has null dates: $booking');
return false;
}
// Parse the dates
final DateTime bookingStartDate = DateTime.parse(
booking['waktu_mulai'],
);
final DateTime bookingEndDate = DateTime.parse(
booking['waktu_selesai'],
);
// A booking overlaps with our date range if:
// 1. The booking ends after or on our start date AND
// 2. The booking starts before or on our end date
return !bookingEndDate.isBefore(startDateTime) &&
!bookingStartDate.isAfter(endDateTime);
}).toList();
debugPrint(
'📅 Found ${bookingsInRange.length} bookings in the specified range from booked_detail table',
);
// Debug the booking details
if (bookingsInRange.isNotEmpty) {
for (var booking in bookingsInRange) {
debugPrint(
'📋 Booking ID: ${booking['sewa_aset_id'] ?? booking['id']}',
);
debugPrint(' - Start: ${booking['waktu_mulai']}');
debugPrint(' - End: ${booking['waktu_selesai']}');
debugPrint(' - Quantity: ${booking['kuantitas']}');
}
}
return bookingsInRange.map((booking) {
final Map<String, dynamic> result = Map<String, dynamic>.from(booking);
// Use sewa_aset_id as the id if available
if (booking['sewa_aset_id'] != null) {
result['id'] = booking['sewa_aset_id'];
}
return result;
}).toList();
} catch (e) {
debugPrint('❌ Error getting daily bookings: $e');
return [];
}
}
bool _isBeforeToday(DateTime date) {
final today = DateTime.now();
final todayDate = DateTime(today.year, today.month, today.day);
final checkDate = DateTime(date.year, date.month, date.day);
// Return true if date is today or before today (meaning it should be disabled)
return !checkDate.isAfter(todayDate);
}
// Get tagihan sewa by sewa_aset_id
Future<Map<String, dynamic>?> getTagihanSewa(String sewaAsetId) async {
try {
debugPrint('🔍 Fetching tagihan sewa for sewa_aset_id: $sewaAsetId');
final response =
await client
.from('tagihan_sewa')
.select('*')
.eq('sewa_aset_id', sewaAsetId)
.maybeSingle();
if (response == null) {
debugPrint('⚠️ No tagihan sewa found for sewa_aset_id: $sewaAsetId');
return null;
}
debugPrint('✅ Tagihan sewa found: ${response['id']}');
return response;
} catch (e) {
debugPrint('❌ Error fetching tagihan sewa: $e');
return null;
}
}
// Get sewa_aset details with aset data
Future<Map<String, dynamic>?> getSewaAsetWithAsetData(
String sewaAsetId,
) async {
try {
debugPrint('🔍 Fetching sewa_aset with aset data for id: $sewaAsetId');
// First get the sewa_aset record
debugPrint('📊 Executing query: FROM sewa_aset WHERE id = $sewaAsetId');
final sewaResponse =
await client
.from('sewa_aset')
.select('*')
.eq('id', sewaAsetId)
.maybeSingle();
if (sewaResponse == null) {
debugPrint('⚠️ No sewa_aset found with id: $sewaAsetId');
return null;
}
debugPrint('📋 Raw sewa_aset response:');
sewaResponse.forEach((key, value) {
debugPrint(' $key: $value');
});
// Get the aset_id from the sewa_aset record
final asetId = sewaResponse['aset_id'];
if (asetId == null) {
debugPrint('⚠️ sewa_aset record has no aset_id');
return sewaResponse;
}
debugPrint('🔍 Found aset_id: $asetId, now fetching aset details');
// Get the aset details
final asetResponse =
await client.from('aset').select('*').eq('id', asetId).maybeSingle();
if (asetResponse == null) {
debugPrint('⚠️ No aset found with id: $asetId');
return sewaResponse;
}
// Combine the data
final result = Map<String, dynamic>.from(sewaResponse);
result['aset_detail'] = asetResponse;
debugPrint('✅ Combined sewa_aset and aset data successfully');
debugPrint('📋 Final combined data:');
result.forEach((key, value) {
if (key != 'aset_detail') {
// Skip the nested object for clearer output
debugPrint(' $key: $value');
}
});
// Specifically check waktu_mulai and waktu_selesai
debugPrint('⏰ CRITICAL TIME FIELDS CHECK:');
debugPrint(' waktu_mulai exists: ${result.containsKey('waktu_mulai')}');
debugPrint(' waktu_mulai value: ${result['waktu_mulai']}');
debugPrint(
' waktu_selesai exists: ${result.containsKey('waktu_selesai')}',
);
debugPrint(' waktu_selesai value: ${result['waktu_selesai']}');
return result;
} catch (e) {
debugPrint('❌ Error fetching sewa_aset with aset data: $e');
debugPrint(' Stack trace: ${StackTrace.current}');
return null;
}
}
// Fungsi untuk mengambil foto pertama dari paket
Future<String?> _getFirstPaketPhoto(String paketId) async {
try {
debugPrint('Fetching first photo for paket ID: $paketId');
final responsePhoto =
await client
.from('foto_aset')
.select('*')
.eq('id_paket', paketId)
.limit(1)
.maybeSingle();
if (responsePhoto != null) {
debugPrint(
'Found photo for paket $paketId: ${responsePhoto['foto_aset']}',
);
return responsePhoto['foto_aset'];
}
return null;
} catch (e) {
debugPrint('Error fetching photo for paket $paketId: $e');
return null;
}
}
// Get paket data with their associated satuan waktu sewa data
Future<List<dynamic>> getPakets() async {
try {
final response = await client
.from('paket')
.select('*')
.order('created_at');
final List<dynamic> pakets = response;
// Fetch satuan waktu sewa data for each paket
for (var paket in pakets) {
// Fetch the first photo for this paket
final paketId = paket['id'];
final photoUrl = await _getFirstPaketPhoto(paketId);
if (photoUrl != null) {
paket['gambar_url'] = photoUrl;
}
final swsResponse = await client
.from('satuan_waktu_sewa')
.select('*, satuan_waktu(id, nama_satuan_waktu)')
.eq('paket_id', paket['id']);
// Transform the response to include nama_satuan_waktu
final List<Map<String, dynamic>> formattedSWS = [];
for (var sws in swsResponse) {
final Map<String, dynamic> formattedItem = {...sws};
if (sws['satuan_waktu'] != null) {
formattedItem['nama_satuan_waktu'] =
sws['satuan_waktu']['nama_satuan_waktu'];
}
formattedSWS.add(formattedItem);
}
paket['satuanWaktuSewa'] = formattedSWS;
}
return pakets;
} catch (e) {
debugPrint('Error getting pakets: $e');
rethrow;
}
}
// Order a paket
Future<bool> orderPaket({
required String userId,
required String paketId,
required String satuanWaktuSewaId,
required int durasi,
required int totalHarga,
}) async {
try {
// Get satuan waktu sewa details to determine waktu_mulai and waktu_selesai
final swsResponse =
await client
.from('satuan_waktu_sewa')
.select('*, satuan_waktu(id, nama)')
.eq('id', satuanWaktuSewaId)
.single();
// Calculate waktu_mulai and waktu_selesai based on satuan waktu
final DateTime now = DateTime.now();
final DateTime waktuMulai = now.add(Duration(days: 1)); // Start tomorrow
// Default to hourly if not specified
String satuanWaktu = 'jam';
if (swsResponse['satuan_waktu'] != null &&
swsResponse['satuan_waktu']['nama'] != null) {
satuanWaktu = swsResponse['satuan_waktu']['nama'];
}
// Calculate waktu_selesai based on satuan waktu and durasi
DateTime waktuSelesai;
if (satuanWaktu.toLowerCase() == 'hari') {
waktuSelesai = waktuMulai.add(Duration(days: durasi));
} else {
waktuSelesai = waktuMulai.add(Duration(hours: durasi));
}
// Create the order
final sewa = {
'user_id': userId,
'paket_id': paketId,
'satuan_waktu_sewa_id': satuanWaktuSewaId,
'kuantitas': 1, // Default to 1 for packages
'durasi': durasi,
'total_harga': totalHarga,
'status': 'MENUNGGU_PEMBAYARAN',
'waktu_mulai': waktuMulai.toIso8601String(),
'waktu_selesai': waktuSelesai.toIso8601String(),
};
final response = await client.from('sewa_paket').insert(sewa).select();
if (response.isNotEmpty) {
return true;
}
return false;
} catch (e) {
debugPrint('Error ordering paket: $e');
return false;
}
}
// Get photos for a package
Future<List<String>> getFotoPaket(String paketId) async {
try {
final response = await client
.from('foto_aset')
.select('foto_aset')
.eq('id_paket', paketId)
.order('created_at');
if (response != null && response.isNotEmpty) {
return response.map<String>((item) => item['foto_aset'] as String).toList();
}
return [];
} catch (e) {
debugPrint('Error getting package photos: $e');
return [];
}
}
// Get items included in a package with additional asset details
Future<List<Map<String, dynamic>>> getPaketItems(String paketId) async {
debugPrint('🔄 [1/3] Starting to fetch items for paket ID: $paketId');
try {
// 1. First, get the basic package items (aset_id and kuantitas)
debugPrint('🔍 [2/3] Querying paket_item table for paket_id: $paketId');
final response = await client
.from('paket_item')
.select('''
aset_id,
kuantitas
''')
.eq('paket_id', paketId);
debugPrint('📊 Raw response from paket_item query:');
debugPrint(response.toString());
if (response == null) {
debugPrint('❌ [ERROR] Null response from paket_item query');
return [];
}
if (response.isEmpty) {
debugPrint(' [INFO] No items found in paket_item for paket ID: $paketId');
return [];
}
debugPrint('✅ [SUCCESS] Found ${response.length} items in paket_item');
final List<Map<String, dynamic>> enrichedItems = [];
// Process each item to fetch additional details
debugPrint('🔄 [3/3] Processing ${response.length} items to fetch asset details');
for (var item in response) {
final String? asetId = item['aset_id']?.toString();
final int kuantitas = item['kuantitas'] ?? 1;
debugPrint('\n🔍 Processing item:');
debugPrint(' - Raw item data: $item');
debugPrint(' - aset_id: $asetId');
debugPrint(' - kuantitas: $kuantitas');
if (asetId == null || asetId.isEmpty) {
debugPrint('⚠️ [WARNING] Skipping item with null/empty aset_id');
continue;
}
try {
// 1. Get asset name from aset table
debugPrint(' - Querying aset table for id: $asetId');
final asetResponse = await client
.from('aset')
.select('id, nama, deskripsi')
.eq('id', asetId)
.maybeSingle();
debugPrint(' - Aset response: ${asetResponse?.toString() ?? 'null'}');
if (asetResponse == null) {
debugPrint('⚠️ [WARNING] No asset found with id: $asetId');
enrichedItems.add({
'aset_id': asetId,
'kuantitas': kuantitas,
'nama_aset': 'Item tidak diketahui',
'foto_aset': '',
'semua_foto': <String>[],
'error': 'Asset not found'
});
continue;
}
// 2. Get only the first photo from foto_aset table
debugPrint(' - Querying first photo for id_aset: $asetId');
final fotoResponse = await client
.from('foto_aset')
.select('foto_aset')
.eq('id_aset', asetId)
.order('created_at', ascending: true)
.limit(1);
String? fotoUtama = '';
List<String> semuaFoto = [];
if (fotoResponse.isNotEmpty) {
final firstFoto = fotoResponse.first['foto_aset']?.toString();
if (firstFoto != null && firstFoto.isNotEmpty) {
fotoUtama = firstFoto;
semuaFoto = [firstFoto];
debugPrint(' - Found photo: $firstFoto');
} else {
debugPrint(' - No valid photo URL found');
}
} else {
debugPrint(' - No photos found for asset $asetId');
}
// 4. Combine all data
final enrichedItem = {
'aset_id': asetId,
'kuantitas': kuantitas,
'nama_aset': asetResponse['nama']?.toString() ?? 'Nama tidak tersedia',
'foto_aset': fotoUtama,
'semua_foto': semuaFoto,
'debug': {
'aset_query': asetResponse,
'foto_count': semuaFoto.length
}
};
enrichedItems.add(enrichedItem);
// Debug log
debugPrint('✅ Successfully processed item:');
debugPrint(' - Aset ID: $asetId');
debugPrint(' - Nama: ${enrichedItem['nama_aset']}');
debugPrint(' - Kuantitas: $kuantitas');
debugPrint(' - Jumlah Foto: ${semuaFoto.length}');
if (semuaFoto.isNotEmpty) {
debugPrint(' - Foto Utama: ${semuaFoto.first}');
}
} catch (e) {
debugPrint('❌ Error processing asset $asetId: $e');
// Still add the basic item even if we couldn't fetch additional details
enrichedItems.add({
'aset_id': asetId,
'kuantitas': item['kuantitas'],
'nama_aset': 'Nama Aset Tidak Ditemukan',
'foto_aset': '',
'semua_foto': <String>[],
});
}
}
debugPrint('✅ Successfully fetched ${enrichedItems.length} items with details');
return enrichedItems;
} catch (e, stackTrace) {
debugPrint('❌ Error getting package items for paket $paketId: $e');
debugPrint('Stack trace: $stackTrace');
return [];
}
}
// Get available bank accounts for payment
Future<List<Map<String, dynamic>>> getBankAccounts() async {
try {
final response = await client
.from('bank_accounts')
.select('*')
.eq('is_active', true)
.order('bank_name');
if (response != null && response.isNotEmpty) {
return List<Map<String, dynamic>>.from(response);
}
return [];
} catch (e) {
debugPrint('Error getting bank accounts: $e');
return [];
}
}
}