import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart' as http; import 'package:mobile_pos/Provider/product_provider.dart'; import 'package:mobile_pos/constant.dart'; import '../../../Const/api_config.dart'; import '../../../Provider/profile_provider.dart'; import '../../../Provider/transactions_provider.dart'; import '../../../Repository/constant_functions.dart'; import '../../../http_client/custome_http_client.dart'; import '../../../http_client/customer_http_client_get.dart'; import '../../../model/balance_sheet_model.dart' as bs; import '../../../model/bill_wise_loss_profit_report_model.dart' as bwlprm; import '../../../model/cashflow_model.dart' as cf; import '../../../model/loss_profit_model.dart' as lpmodel; import '../../../model/product_history_model.dart' as phlm; import '../../../model/sale_transaction_model.dart'; import '../../../model/subscription_report_model.dart' as srm; import '../../../model/tax_report_model.dart' as trm; import '../../Customers/Provider/customer_provider.dart'; class SaleRepo { Future> fetchSalesList({ bool? salesReturn, String? type, String? fromDate, String? toDate, }) async { final client = CustomHttpClientGet(client: http.Client()); // Manually build query string to preserve order final List queryList = []; if (salesReturn != null && salesReturn) { queryList.add('returned-sales=true'); } if (type != null && type.isNotEmpty) { queryList.add('duration=$type'); } if (type == 'custom_date' && fromDate != null && toDate != null && fromDate.isNotEmpty && toDate.isNotEmpty) { queryList.add('from_date=$fromDate'); queryList.add('to_date=$toDate'); } final String queryString = queryList.join('&'); final Uri uri = Uri.parse('${APIConfig.url}/sales${queryString.isNotEmpty ? '?$queryString' : ''}'); print(uri); final response = await client.get(url: uri); if (response.statusCode == 200) { final parsed = jsonDecode(response.body) as Map; final list = parsed['data'] as List; return list.map((json) => SalesTransactionModel.fromJson(json)).toList(); } else { throw Exception('Failed to fetch Sales List. Status code: ${response.statusCode}'); } } Future getSingleSale(int id) async { final uri = Uri.parse('${APIConfig.url}/sales/$id'); try { CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client()); final response = await clientGet.get(url: uri); print("Fetch Single Single Status: ${response.statusCode}"); print("Fetch Single Single Body: ${response.body}"); if (response.statusCode == 200) { final parsed = jsonDecode(response.body); return SalesTransactionModel.fromJson(parsed['data']); } else { throw Exception("Failed to fetch sale details"); } } catch (e) { throw Exception("Error fetching sale: $e"); } } /// Create Sale Future createSale({ required WidgetRef ref, required BuildContext context, required num? partyId, required String? customerPhone, required String purchaseDate, required num discountAmount, required num discountPercent, required num unRoundedTotalAmount, required num totalAmount, required num roundingAmount, required num dueAmount, required num vatAmount, required num vatPercent, required num? vatId, required num changeAmount, required bool isPaid, required String paymentType, required String roundedOption, required List products, required String discountType, required num shippingCharge, String? note, File? image, }) async { // 1. Prepare Fields final fields = _buildCommonFields( purchaseDate: purchaseDate, discountAmount: discountAmount, discountPercent: discountPercent, totalAmount: totalAmount, dueAmount: dueAmount, vatAmount: vatAmount, vatPercent: vatPercent, changeAmount: changeAmount, isPaid: isPaid, paymentType: paymentType, discountType: discountType, shippingCharge: shippingCharge, roundedOption: roundedOption, roundingAmount: roundingAmount, unRoundedTotalAmount: unRoundedTotalAmount, products: products, note: note, partyId: partyId, vatId: vatId, ); if (customerPhone != null) fields['customer_phone'] = customerPhone; // 2. Submit Request final response = await _submitRequest( ref: ref, context: context, url: '${APIConfig.url}/sales', method: 'POST', fields: fields, image: image, ); print('Sales Response Data: ${response?.body}'); // 3. Handle Success if (response != null && response.statusCode == 200) { final parsedData = jsonDecode(response.body); _refreshProviders(ref); return SalesTransactionModel.fromJson(parsedData['data']); } else if (response != null) { _handleError(context, response); } return null; } /// Update Sale Future updateSale({ required WidgetRef ref, required BuildContext context, required num id, required num? partyId, required String purchaseDate, required num discountAmount, required num discountPercent, required num unRoundedTotalAmount, required num totalAmount, required num dueAmount, required num vatAmount, required num vatPercent, required num? vatId, required num changeAmount, required num roundingAmount, required bool isPaid, required String paymentType, required String roundedOption, required List products, required String discountType, required num shippingCharge, String? note, File? image, }) async { // 1. Prepare Fields final fields = _buildCommonFields( purchaseDate: purchaseDate, discountAmount: discountAmount, discountPercent: discountPercent, totalAmount: totalAmount, dueAmount: dueAmount, vatAmount: vatAmount, vatPercent: vatPercent, changeAmount: changeAmount, isPaid: isPaid, paymentType: paymentType, discountType: discountType, shippingCharge: shippingCharge, roundedOption: roundedOption, roundingAmount: roundingAmount, unRoundedTotalAmount: unRoundedTotalAmount, products: products, note: note, partyId: partyId, vatId: vatId, ); // Add Method Override for Update fields['_method'] = 'put'; // 2. Submit Request final response = await _submitRequest( ref: ref, context: context, url: '${APIConfig.url}/sales/$id', method: 'POST', // Multipart uses POST with _method field for PUT behavior usually fields: fields, image: image, ); // 3. Handle Success if (response != null && response.statusCode == 200) { EasyLoading.showSuccess('Updated successful!'); _refreshProviders(ref); Navigator.pop(context); } else if (response != null) { _handleError(context, response); } } // ------------------------------------------ // Private Helper Methods (The Simplification) // ------------------------------------------ Map _buildCommonFields({ required String purchaseDate, required num discountAmount, required num discountPercent, required num totalAmount, required num dueAmount, required num vatAmount, required num vatPercent, required num changeAmount, required bool isPaid, required String paymentType, required String discountType, required num shippingCharge, required String roundedOption, required num roundingAmount, required num unRoundedTotalAmount, required List products, String? note, num? partyId, num? vatId, }) { final Map fields = { 'saleDate': purchaseDate, 'discountAmount': discountAmount.toString(), 'discount_percent': discountPercent.toString(), 'totalAmount': totalAmount.toString(), 'dueAmount': dueAmount.toString(), 'paidAmount': (totalAmount - dueAmount).toString(), 'change_amount': changeAmount.toString(), 'vat_amount': vatAmount.toString(), 'vat_percent': vatPercent.toString(), 'isPaid': isPaid.toString(), 'payments': paymentType, 'discount_type': discountType, 'shipping_charge': shippingCharge.toString(), 'rounding_option': roundedOption, 'rounding_amount': roundingAmount.toStringAsFixed(2), 'actual_total_amount': unRoundedTotalAmount.toString(), 'note': note ?? '', 'products': jsonEncode(products.map((e) => e.toJson()).toList()), }; if (partyId != null) fields['party_id'] = partyId.toString(); if (vatId != null) fields['vat_id'] = vatId.toString(); return fields; } Future _submitRequest({ required WidgetRef ref, required BuildContext context, required String url, required String method, required Map fields, File? image, }) async { final uri = Uri.parse(url); try { var request = http.MultipartRequest(method, uri); // Add Headers request.headers.addAll({ 'Accept': 'application/json', 'Authorization': await getAuthToken(), 'Content-Type': 'multipart/form-data', }); // Add Fields request.fields.addAll(fields); // Add Image if (image != null) { request.files.add(await http.MultipartFile.fromPath('image', image.path)); } CustomHttpClient customHttpClient = CustomHttpClient( client: http.Client(), ref: ref, context: context, ); var streamedResponse = await customHttpClient.uploadFile( url: uri, file: image, fileFieldName: 'image', fields: request.fields, contentType: 'multipart/form-data', ); print('POST Sales Data ------------------->\n${request.fields}'); return await http.Response.fromStream(streamedResponse); } catch (error) { EasyLoading.dismiss(); final errorMessage = error.toString().replaceFirst('Exception: ', ''); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(errorMessage), backgroundColor: kMainColor), ); return null; } } void _refreshProviders(WidgetRef ref) { ref.refresh(productProvider); ref.refresh(partiesProvider); ref.refresh(salesTransactionProvider); ref.refresh(businessInfoProvider); ref.refresh(getExpireDateProvider(ref)); ref.refresh(summaryInfoProvider); } void _handleError(BuildContext context, http.Response response) { EasyLoading.dismiss(); try { final parsedData = jsonDecode(response.body); print('reponse :${parsedData}'); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Operation failed: ${parsedData['message'] ?? response.reasonPhrase}')), ); } catch (_) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Operation failed: ${response.statusCode}')), ); } } Future getLossProfit({ String? type, String? fromDate, String? toDate, }) async { try { CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client()); final List queryList = []; if (type != null && type.isNotEmpty) { queryList.add('duration=$type'); } if (type == 'custom_date' && fromDate != null && toDate != null && fromDate.isNotEmpty && toDate.isNotEmpty) { queryList.add('from_date=$fromDate'); queryList.add('to_date=$toDate'); } final String queryString = queryList.join('&'); final Uri uri = Uri.parse('${APIConfig.url}/reports/loss-profit${queryString.isNotEmpty ? '?$queryString' : ''}'); final response = await clientGet.get(url: uri); print('Response Status: ${response.statusCode}'); print('Response Body: ${response.body}'); if (response.statusCode == 200) { final parsed = jsonDecode(response.body); if (parsed == null) { throw Exception("Response is null"); } if (parsed['data'] == null) { return lpmodel.LossProfitModel.fromJson(parsed); } return lpmodel.LossProfitModel.fromJson(parsed['data']); } else { throw Exception("Failed to fetch loss profit: ${response.statusCode} - ${response.body}"); } } catch (e, stack) { throw Exception("Error fetching loss profit: $e"); } } Future getCashflow({ String? type, String? fromDate, String? toDate, }) async { try { CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client()); final List queryList = []; if (type != null && type.isNotEmpty) { queryList.add('duration=$type'); } if (type == 'custom_date' && fromDate != null && toDate != null && fromDate.isNotEmpty && toDate.isNotEmpty) { queryList.add('from_date=$fromDate'); queryList.add('to_date=$toDate'); } final String queryString = queryList.join('&'); final Uri uri = Uri.parse('${APIConfig.url}/reports/cashflow${queryString.isNotEmpty ? '?$queryString' : ''}'); final response = await clientGet.get(url: uri); if (response.statusCode == 200) { final parsed = jsonDecode(response.body); return cf.CashflowModel.fromJson(parsed); } else { throw Exception("Failed to fetch sale details: ${response.statusCode} - ${response.body}"); } } catch (e) { throw Exception("Error fetching sale: $e"); } } Future getBalanceSheet({ String? type, String? fromDate, String? toDate, }) async { try { CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client()); final List queryList = []; if (type != null && type.isNotEmpty) { queryList.add('duration=$type'); } if (type == 'custom_date' && fromDate != null && toDate != null && fromDate.isNotEmpty && toDate.isNotEmpty) { queryList.add('from_date=$fromDate'); queryList.add('to_date=$toDate'); } final String queryString = queryList.join('&'); final Uri uri = Uri.parse('${APIConfig.url}/reports/balance-sheet${queryString.isNotEmpty ? '?$queryString' : ''}'); final response = await clientGet.get(url: uri); if (response.statusCode == 200) { final parsed = jsonDecode(response.body); return bs.BalanceSheetModel.fromJson(parsed); } else { throw Exception("Failed to fetch balance sheet details: ${response.statusCode} - ${response.body}"); } } catch (e) { throw Exception("Error fetching balance sheet: $e"); } } Future> getSubscriptionReport({ String? type, String? fromDate, String? toDate, }) async { try { CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client()); final List queryList = []; if (type != null && type.isNotEmpty) { queryList.add('duration=$type'); } if (type == 'custom_date' && fromDate != null && toDate != null && fromDate.isNotEmpty && toDate.isNotEmpty) { queryList.add('from_date=$fromDate'); queryList.add('to_date=$toDate'); } final String queryString = queryList.join('&'); final Uri uri = Uri.parse('${APIConfig.url}/reports/subscription${queryString.isNotEmpty ? '?$queryString' : ''}'); final response = await clientGet.get(url: uri); if (response.statusCode == 200) { final parsed = jsonDecode(response.body); return [...?parsed?["data"].map((x) => srm.SubscriptionReportModel.fromJson(x))]; } else { throw Exception("Failed to fetch subscription report details: ${response.statusCode} - ${response.body}"); } } catch (e) { throw Exception("Error fetching subscription report: $e"); } } Future getTaxReport({ String? type, String? fromDate, String? toDate, }) async { try { CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client()); final List queryList = []; if (type != null && type.isNotEmpty) { queryList.add('duration=$type'); } if (type == 'custom_date' && fromDate != null && toDate != null && fromDate.isNotEmpty && toDate.isNotEmpty) { queryList.add('from_date=$fromDate'); queryList.add('to_date=$toDate'); } final String queryString = queryList.join('&'); final Uri uri = Uri.parse('${APIConfig.url}/reports/tax${queryString.isNotEmpty ? '?$queryString' : ''}'); final response = await clientGet.get(url: uri); if (response.statusCode == 200) { final parsed = jsonDecode(response.body); return trm.TaxReportModel.fromJson(parsed); } else { throw Exception("Failed to fetch tax report details: ${response.statusCode} - ${response.body}"); } } catch (e) { throw Exception("Error fetching tax report: $e"); } } Future getBillWiseLossProfitReport({ String? type, String? fromDate, String? toDate, }) async { try { CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client()); final List queryList = []; if (type != null && type.isNotEmpty) { queryList.add('duration=$type'); } if (type == 'custom_date' && fromDate != null && toDate != null && fromDate.isNotEmpty && toDate.isNotEmpty) { queryList.add('from_date=$fromDate'); queryList.add('to_date=$toDate'); } final String queryString = queryList.join('&'); final Uri uri = Uri.parse('${APIConfig.url}/reports/bill-wise-profit${queryString.isNotEmpty ? '?$queryString' : ''}'); final response = await clientGet.get(url: uri); if (response.statusCode == 200) { final parsed = jsonDecode(response.body); return bwlprm.BillWiseLossProfitReportModel.fromJson(parsed); } else { throw Exception("Failed to fetch tax report details: ${response.statusCode} - ${response.body}"); } } catch (e) { throw Exception("Error fetching tax report: $e"); } } Future getProductSaleHistoryReport({ String? type, String? fromDate, String? toDate, }) async { try { CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client()); final List queryList = []; if (type != null && type.isNotEmpty) { queryList.add('duration=$type'); } if (type == 'custom_date' && fromDate != null && toDate != null && fromDate.isNotEmpty && toDate.isNotEmpty) { queryList.add('from_date=$fromDate'); queryList.add('to_date=$toDate'); } final String queryString = queryList.join('&'); final Uri uri = Uri.parse('${APIConfig.url}/reports/product-sale-history${queryString.isNotEmpty ? '?$queryString' : ''}'); final response = await clientGet.get(url: uri); if (response.statusCode == 200) { final parsed = jsonDecode(response.body); return phlm.ProductHistoryListModel.fromJson(parsed); } else { throw Exception("Failed to fetch tax report details: ${response.statusCode} - ${response.body}"); } } catch (e) { throw Exception("Error fetching tax report: $e"); } } Future getProductSaleHistoryReportDetails({ required int productId, String? type, String? fromDate, String? toDate, }) async { try { CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client()); final List queryList = []; if (type != null && type.isNotEmpty) { queryList.add('duration=$type'); } if (type == 'custom_date' && fromDate != null && toDate != null && fromDate.isNotEmpty && toDate.isNotEmpty) { queryList.add('from_date=$fromDate'); queryList.add('to_date=$toDate'); } final String queryString = queryList.join('&'); final Uri uri = Uri.parse( '${APIConfig.url}/reports/product-sale-history/$productId${queryString.isNotEmpty ? '?$queryString' : ''}'); final response = await clientGet.get(url: uri); if (response.statusCode == 200) { final parsed = jsonDecode(response.body); return phlm.ProductHistoryDetailsModel.fromJson(parsed); } else { throw Exception("Failed to fetch tax report details: ${response.statusCode} - ${response.body}"); } } catch (e) { throw Exception("Error fetching tax report: $e"); } } Future getProductPurchaseHistoryReportDetails({ required int productId, String? type, String? fromDate, String? toDate, }) async { try { CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client()); final List queryList = []; if (type != null && type.isNotEmpty) { queryList.add('duration=$type'); } if (type == 'custom_date' && fromDate != null && toDate != null && fromDate.isNotEmpty && toDate.isNotEmpty) { queryList.add('from_date=$fromDate'); queryList.add('to_date=$toDate'); } final String queryString = queryList.join('&'); final Uri uri = Uri.parse( '${APIConfig.url}/reports/product-purchase-history/$productId${queryString.isNotEmpty ? '?$queryString' : ''}'); final response = await clientGet.get(url: uri); if (response.statusCode == 200) { final parsed = jsonDecode(response.body); return phlm.ProductHistoryDetailsModel.fromJson(parsed); } else { throw Exception("Failed to fetch tax report details: ${response.statusCode} - ${response.body}"); } } catch (e) { throw Exception("Error fetching tax report: $e"); } } } class CartSaleProducts { final num stockId; final num productId; final num? price; final num? discount; final String productName; final num? quantities; CartSaleProducts({ required this.productName, required this.stockId, this.discount, required this.productId, required this.price, required this.quantities, }); Map toJson() => { 'stock_id': stockId, 'product_id': productId, 'product_name': productName, 'price': price, 'quantities': quantities, 'discount': discount, }; }