Files
kulakpos_app/lib/Screens/Sales/Repo/sales_repo.dart
2026-02-07 15:57:09 +07:00

705 lines
23 KiB
Dart

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<List<SalesTransactionModel>> 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<String> 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<String, dynamic>;
final list = parsed['data'] as List<dynamic>;
return list.map((json) => SalesTransactionModel.fromJson(json)).toList();
} else {
throw Exception('Failed to fetch Sales List. Status code: ${response.statusCode}');
}
}
Future<SalesTransactionModel?> 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<SalesTransactionModel?> 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<CartSaleProducts> 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<void> 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<CartSaleProducts> 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<String, String> _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<CartSaleProducts> products,
String? note,
num? partyId,
num? vatId,
}) {
final Map<String, String> 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<http.Response?> _submitRequest({
required WidgetRef ref,
required BuildContext context,
required String url,
required String method,
required Map<String, String> 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<lpmodel.LossProfitModel> getLossProfit({
String? type,
String? fromDate,
String? toDate,
}) async {
try {
CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client());
final List<String> 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<cf.CashflowModel> getCashflow({
String? type,
String? fromDate,
String? toDate,
}) async {
try {
CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client());
final List<String> 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<bs.BalanceSheetModel> getBalanceSheet({
String? type,
String? fromDate,
String? toDate,
}) async {
try {
CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client());
final List<String> 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<List<srm.SubscriptionReportModel>> getSubscriptionReport({
String? type,
String? fromDate,
String? toDate,
}) async {
try {
CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client());
final List<String> 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<srm.SubscriptionReportModel>((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<trm.TaxReportModel> getTaxReport({
String? type,
String? fromDate,
String? toDate,
}) async {
try {
CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client());
final List<String> 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<bwlprm.BillWiseLossProfitReportModel> getBillWiseLossProfitReport({
String? type,
String? fromDate,
String? toDate,
}) async {
try {
CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client());
final List<String> 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<phlm.ProductHistoryListModel> getProductSaleHistoryReport({
String? type,
String? fromDate,
String? toDate,
}) async {
try {
CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client());
final List<String> 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<phlm.ProductHistoryDetailsModel> getProductSaleHistoryReportDetails({
required int productId,
String? type,
String? fromDate,
String? toDate,
}) async {
try {
CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client());
final List<String> 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<phlm.ProductHistoryDetailsModel> getProductPurchaseHistoryReportDetails({
required int productId,
String? type,
String? fromDate,
String? toDate,
}) async {
try {
CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client());
final List<String> 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<String, dynamic> toJson() => {
'stock_id': stockId,
'product_id': productId,
'product_name': productName,
'price': price,
'quantities': quantities,
'discount': discount,
};
}