first commit

This commit is contained in:
2026-02-07 15:57:09 +07:00
commit 157096f164
1153 changed files with 415766 additions and 0 deletions

View File

@@ -0,0 +1,502 @@
import '../../../model/sale_transaction_model.dart';
import '../../../widgets/multipal payment mathods/model/payment_transaction_model.dart';
class PurchaseTransaction {
PurchaseTransaction({
this.id,
this.partyId,
this.businessId,
this.userId,
this.discountAmount,
this.discountPercent,
this.discountType,
this.shippingCharge,
this.dueAmount,
this.paidAmount,
this.changeAmount,
this.totalAmount,
this.invoiceNumber,
this.isPaid,
this.paymentTypeId,
this.paymentType,
this.purchaseDate,
this.createdAt,
this.updatedAt,
this.user,
this.party,
this.details,
this.purchaseReturns,
this.transactions, // New Field
this.vatAmount,
this.vatId,
this.vatPercent,
this.vat,
this.branch,
});
PurchaseTransaction.fromJson(dynamic json) {
id = json['id'];
partyId = json['party_id'];
businessId = json['business_id'];
userId = json['user_id'];
discountAmount = json['discountAmount'];
discountPercent = json['discount_percent'];
shippingCharge = json['shipping_charge'];
discountType = json['discount_type'];
dueAmount = json['dueAmount'];
changeAmount = json['change_amount'];
vatAmount = json['vat_amount'];
vatPercent = json['vat_percent'];
vatId = json['vat_id'];
paidAmount = json['paidAmount'];
totalAmount = json['totalAmount'];
invoiceNumber = json['invoiceNumber'];
isPaid = json['isPaid'];
paymentTypeId = int.tryParse(json["payment_type_id"].toString());
vat = json['vat'] != null ? PurchaseVat.fromJson(json['vat']) : null;
purchaseDate = json['purchaseDate'];
createdAt = json['created_at'];
updatedAt = json['updated_at'];
paymentType = json['payment_type'] != null ? PaymentType.fromJson(json['payment_type']) : null;
branch = json['branch'] != null ? Branch.fromJson(json['branch']) : null;
user = json['user'] != null ? User.fromJson(json['user']) : null;
party = json['party'] != null ? Party.fromJson(json['party']) : null;
if (json['details'] != null) {
details = [];
json['details'].forEach((v) {
details?.add(PurchaseDetails.fromJson(v));
});
}
if (json['purchase_returns'] != null) {
purchaseReturns = [];
json['purchase_returns'].forEach((v) {
purchaseReturns?.add(PurchaseReturn.fromJson(v));
});
}
// New List from JSON
if (json['transactions'] != null) {
transactions = [];
json['transactions'].forEach((v) {
transactions?.add(PaymentsTransaction.fromJson(v));
});
}
}
num? id;
num? partyId;
num? businessId;
num? userId;
num? discountAmount;
num? discountPercent;
num? shippingCharge;
String? discountType;
num? dueAmount;
num? paidAmount;
num? changeAmount;
num? vatAmount;
num? vatPercent;
num? vatId;
num? totalAmount;
String? invoiceNumber;
bool? isPaid;
int? paymentTypeId;
PaymentType? paymentType;
Branch? branch;
String? purchaseDate;
String? createdAt;
String? updatedAt;
User? user;
Party? party;
List<PurchaseDetails>? details;
List<PurchaseReturn>? purchaseReturns;
List<PaymentsTransaction>? transactions; // Added
PurchaseVat? vat;
}
class PurchaseDetails {
PurchaseDetails({
this.id,
this.purchaseId,
this.productId,
this.productPurchasePrice,
this.quantities,
this.productWholeSalePrice,
this.productSalePrice,
this.productDealerPrice,
this.productStock,
this.profitPercent,
this.mfgDate,
this.expireDate,
this.stockId,
this.product,
this.stock,
});
PurchaseDetails.fromJson(dynamic json) {
id = json['id'];
purchaseId = json['purchase_id'];
productId = json['product_id'];
productPurchasePrice = json['productPurchasePrice'];
quantities = json['quantities'];
productDealerPrice = json['productDealerPrice'];
productSalePrice = json['productSalePrice'];
productStock = json['productStock'];
profitPercent = json['profit_percent'];
mfgDate = json['mfg_date'];
expireDate = json['expire_date'];
stockId = json['stock_id']; // Added
productWholeSalePrice = json['productWholeSalePrice'];
product = json['product'] != null ? Product.fromJson(json['product']) : null;
stock = json['stock'] != null ? PurchaseStock.fromJson(json['stock']) : null;
}
num? id;
num? purchaseId;
num? productId;
num? productPurchasePrice;
num? quantities;
num? productDealerPrice;
num? productSalePrice;
num? productWholeSalePrice;
num? productStock;
num? profitPercent;
num? stockId; // Added
PurchaseStock? stock;
String? mfgDate;
String? expireDate;
Product? product;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = id;
map['purchase_id'] = purchaseId;
map['product_id'] = productId;
map['productPurchasePrice'] = productPurchasePrice;
map['quantities'] = quantities;
map['stock_id'] = stockId;
if (product != null) {
map['product'] = product?.toJson();
}
return map;
}
}
class Product {
Product({
this.id,
this.productName,
this.categoryId,
this.category,
this.productType,
this.vatAmount,
this.vatType,
this.vat,
});
Product.fromJson(dynamic json) {
id = json['id'];
productName = json['productName'];
productType = json['product_type'];
categoryId = json['category_id'];
vatAmount = json['vat_amount'];
vatType = json['vat_type'];
category = json['category'] != null ? Category.fromJson(json['category']) : null;
vat = json['vat'] != null ? PurchaseProductVat.fromJson(json['vat']) : null;
}
num? id;
String? productName;
String? productType;
String? vatType;
num? categoryId;
num? vatAmount;
Category? category;
PurchaseProductVat? vat;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = id;
map['productName'] = productName;
map['category_id'] = categoryId;
if (category != null) {
map['category'] = category?.toJson();
}
return map;
}
}
class Category {
Category({
this.id,
this.categoryName,
});
Category.fromJson(dynamic json) {
id = json['id'];
categoryName = json['categoryName'];
}
num? id;
String? categoryName;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = id;
map['categoryName'] = categoryName;
return map;
}
}
class PurchaseStock {
PurchaseStock({
this.id,
this.batchNo,
this.variantName,
this.warehouseId,
});
PurchaseStock.fromJson(dynamic json) {
id = json['id'];
batchNo = json['batch_no'] ?? 'N/A';
variantName = json['variant_name']; // Added
warehouseId = json['warehouse_id']; // Added
}
num? id;
String? batchNo;
String? variantName; // Added
num? warehouseId; // Added
}
class Party {
Party({
this.id,
this.name,
this.email,
this.phone,
this.type,
this.address,
});
Party.fromJson(dynamic json) {
id = json['id'];
name = json['name'];
email = json['email'];
phone = json['phone'];
type = json['type']; // Added based on JSON
address = json['address']; // Added based on JSON
}
num? id;
String? name;
String? email;
String? address;
String? phone;
String? type;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = id;
map['name'] = name;
map['email'] = email;
map['address'] = address;
map['phone'] = phone;
map['type'] = type;
return map;
}
}
class User {
User({
this.id,
this.name,
this.role,
});
User.fromJson(dynamic json) {
id = json['id'];
name = json['name'];
role = json['role'];
}
num? id;
String? name;
String? role;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = id;
map['name'] = name;
map['role'] = role;
return map;
}
}
class Branch {
Branch({
this.id,
this.name,
this.phone,
this.address,
});
Branch.fromJson(dynamic json) {
id = json['id'];
name = json['name'];
phone = json['phone'];
address = json['address'];
}
num? id;
String? name;
String? phone;
String? address;
}
class PurchaseReturn {
PurchaseReturn({
this.id,
this.businessId,
this.purchaseId,
this.invoiceNo,
this.returnDate,
this.createdAt,
this.updatedAt,
this.purchaseReturnDetails,
});
PurchaseReturn.fromJson(dynamic json) {
id = json['id'];
businessId = json['business_id'];
purchaseId = json['purchase_id'];
invoiceNo = json['invoice_no'];
returnDate = json['return_date'];
createdAt = json['created_at'];
updatedAt = json['updated_at'];
if (json['details'] != null) {
purchaseReturnDetails = [];
json['details'].forEach((v) {
purchaseReturnDetails?.add(PurchaseReturnDetails.fromJson(v));
});
}
}
num? id;
num? businessId;
num? purchaseId;
String? invoiceNo;
String? returnDate;
String? createdAt;
String? updatedAt;
List<PurchaseReturnDetails>? purchaseReturnDetails;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = id;
map['business_id'] = businessId;
map['purchase_id'] = purchaseId;
map['invoice_no'] = invoiceNo;
map['return_date'] = returnDate;
map['created_at'] = createdAt;
map['updated_at'] = updatedAt;
if (purchaseReturnDetails != null) {
map['details'] = purchaseReturnDetails?.map((v) => v.toJson()).toList();
}
return map;
}
}
class PurchaseReturnDetails {
PurchaseReturnDetails({
this.id,
this.businessId,
this.purchaseReturnId,
this.purchaseDetailId,
this.returnAmount,
this.returnQty,
});
PurchaseReturnDetails.fromJson(dynamic json) {
id = json['id'];
businessId = json['business_id'];
purchaseReturnId = json['purchase_return_id'];
purchaseDetailId = json['purchase_detail_id'];
returnAmount = json['return_amount'];
returnQty = json['return_qty'];
}
num? id;
num? businessId;
num? purchaseReturnId;
num? purchaseDetailId;
num? returnAmount;
num? returnQty;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = id;
map['business_id'] = businessId;
map['purchase_return_id'] = purchaseReturnId;
map['purchase_detail_id'] = purchaseDetailId;
map['return_amount'] = returnAmount;
map['return_qty'] = returnQty;
return map;
}
}
class PurchaseVat {
PurchaseVat({
this.id,
this.name,
this.rate,
});
PurchaseVat.fromJson(dynamic json) {
id = json['id'];
name = json['name'];
rate = json['rate'];
}
num? id;
String? name;
num? rate;
}
class PaymentType {
PaymentType({
this.id,
this.name,
});
PaymentType.fromJson(dynamic json) {
id = json['id'];
name = json['name'];
}
num? id;
String? name;
}
class PurchaseProductVat {
PurchaseProductVat({
this.id,
this.name,
this.rate,
});
PurchaseProductVat.fromJson(dynamic json) {
id = json['id'];
name = json['name'];
rate = json['rate'];
}
num? id;
num? rate;
String? name;
}

View File

@@ -0,0 +1,330 @@
//ignore_for_file: prefer_typing_uninitialized_variables,unused_local_variable
import 'dart:convert';
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 '../../../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 '../../Customers/Provider/customer_provider.dart';
import '../../../service/check_user_role_permission_provider.dart';
import '../Model/purchase_transaction_model.dart';
class PurchaseRepo {
Future<List<PurchaseTransaction>> fetchPurchaseList({
bool? salesReturn,
String? type,
String? fromDate,
String? toDate,
}) async {
final client = CustomHttpClientGet(client: http.Client());
final List<String> queryList = [];
if (salesReturn != null && salesReturn) {
queryList.add('returned-purchase=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}/purchase${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) => PurchaseTransaction.fromJson(json)).toList();
} else {
throw Exception('Failed to fetch Sales List. Status code: ${response.statusCode}');
}
}
Future<PurchaseTransaction?> createPurchase({
required WidgetRef ref,
required BuildContext context,
required num partyId,
required String purchaseDate,
required num discountAmount,
required num discountPercent,
required num? vatId,
required num totalAmount,
required num vatAmount,
required num vatPercent,
required num dueAmount,
required num changeAmount,
required bool isPaid,
required List<Map<String, dynamic>> paymentType,
required List<CartProductModelPurchase> products,
required String discountType,
required num shippingCharge,
}) async {
final uri = Uri.parse('${APIConfig.url}/purchase');
final body = {
'party_id': partyId,
'vat_id': vatId,
'purchaseDate': purchaseDate,
'discountAmount': discountAmount,
'discount_percent': discountPercent,
'totalAmount': totalAmount,
'vat_amount': vatAmount,
'vat_percent': vatPercent,
'dueAmount': dueAmount,
'paidAmount': totalAmount - dueAmount,
'change_amount': changeAmount,
'isPaid': isPaid,
'payments': paymentType,
'discount_type': discountType,
'shipping_charge': shippingCharge,
'products': products.map((e) => e.toJson()).toList(),
};
print('Purchase Posted data : ${jsonEncode(body)}');
try {
final response = await http.post(
uri,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': await getAuthToken(),
},
body: jsonEncode(body),
);
final parsed = jsonDecode(response.body);
print('Purchase Response : ${response.statusCode}');
print('Purchase Response : $parsed');
if (response.statusCode == 200) {
EasyLoading.showSuccess('Added successful!');
// Refresh providers
ref
..refresh(productProvider)
..refresh(partiesProvider)
..refresh(purchaseTransactionProvider)
..refresh(businessInfoProvider)
..refresh(getExpireDateProvider(ref))
..refresh(summaryInfoProvider);
print('Purchase Response: ${parsed['data']}');
return PurchaseTransaction.fromJson(parsed['data']);
} else {
EasyLoading.dismiss();
_showError(context, 'Purchase creation failed: ${parsed['message']}');
}
} catch (e) {
EasyLoading.dismiss();
_showError(context, 'An error occurred: $e');
}
return null;
}
void _showError(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message)));
}
Future<PurchaseTransaction?> updatePurchase({
required WidgetRef ref,
required BuildContext context,
required num id,
required num partyId,
required num? vatId,
required num vatAmount,
required num vatPercent,
required String purchaseDate,
required num discountAmount,
required num totalAmount,
required num dueAmount,
required num changeAmount,
required bool isPaid,
required List<Map<String, dynamic>> paymentType,
required List<CartProductModelPurchase> products,
}) async {
final uri = Uri.parse('${APIConfig.url}/purchase/$id');
final requestBody = jsonEncode({
'_method': 'put',
'party_id': partyId,
'vat_id': vatId,
'purchaseDate': purchaseDate,
'discountAmount': discountAmount,
'totalAmount': totalAmount,
'vat_amount': vatAmount,
'vat_percent': vatPercent,
'dueAmount': dueAmount,
'paidAmount': totalAmount - dueAmount,
'change_amount': changeAmount,
'isPaid': isPaid,
'payments': paymentType,
'products': products.map((product) => product.toJson()).toList(),
});
try {
CustomHttpClient customHttpClient = CustomHttpClient(client: http.Client(), context: context, ref: ref);
var responseData = await customHttpClient.post(
url: uri,
addContentTypeInHeader: true,
body: requestBody,
// permission: Permit.purchasesUpdate.value,
);
final parsedData = jsonDecode(responseData.body);
print(responseData.statusCode);
print(parsedData);
if (responseData.statusCode == 200) {
EasyLoading.showSuccess('Added successful!');
var data1 = ref.refresh(productProvider);
var data2 = ref.refresh(partiesProvider);
var data3 = ref.refresh(purchaseTransactionProvider);
var data4 = ref.refresh(businessInfoProvider);
ref.refresh(getExpireDateProvider(ref));
Navigator.pop(context);
return PurchaseTransaction.fromJson(parsedData);
} else {
EasyLoading.dismiss();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Purchase creation failed: ${parsedData['message']}')));
return null;
}
} catch (error) {
EasyLoading.dismiss();
// Handle unexpected errors gracefully
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('An error occurred: $error')));
return null;
}
}
Future<void> deletePurchase({
required String id,
required BuildContext context,
required WidgetRef ref,
}) async {
final String apiUrl = '${APIConfig.url}/purchase/$id';
try {
CustomHttpClient customHttpClient = CustomHttpClient(ref: ref, context: context, client: http.Client());
final response = await customHttpClient.delete(
url: Uri.parse(apiUrl),
);
EasyLoading.dismiss();
if (response.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Product deleted successfully')));
var data1 = ref.refresh(productProvider);
Navigator.pop(context); // Assuming you want to close the screen after deletion
Navigator.pop(context); // Assuming you want to close the screen after deletion
// Navigator.pop(context); // Assuming you want to close the screen after deletion
} else {
final parsedData = jsonDecode(response.body);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Failed to delete product: ${parsedData['message']}')));
}
} catch (e) {
EasyLoading.dismiss();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error: $e')));
}
}
Future<PurchaseTransaction?> getSinglePurchase(int id) async {
final uri = Uri.parse('${APIConfig.url}/purchase/$id');
try {
CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client());
final response = await clientGet.get(url: uri);
print("Fetch Single Purchase Status: ${response.statusCode}");
print("Fetch Single Purchase Body: ${response.body}");
if (response.statusCode == 200) {
final parsed = jsonDecode(response.body);
return PurchaseTransaction.fromJson(parsed['data']);
} else {
throw Exception("Failed to fetch purchase details");
}
} catch (e) {
throw Exception("Error fetching purchase: $e");
}
}
}
class CartProductModelPurchase {
num productId;
String? variantName;
num? warehouseId;
String productName;
String productType;
String vatType;
num vatRate;
num vatAmount;
String? brandName;
String? batchNumber;
num? productDealerPrice;
num? productPurchasePrice;
String? expireDate;
String? mfgDate;
num? productSalePrice;
num? profitPercent;
num? productWholeSalePrice;
num? quantities;
num? stock;
CartProductModelPurchase({
required this.productId,
this.variantName,
this.warehouseId, // Change 1: Added to constructor
required this.productName,
required this.productType,
required this.vatRate,
required this.vatAmount,
required this.vatType,
this.brandName,
this.stock,
this.profitPercent,
required this.productDealerPrice,
required this.productPurchasePrice,
required this.productSalePrice,
required this.productWholeSalePrice,
required this.quantities,
this.batchNumber,
this.mfgDate,
this.expireDate,
});
Map<String, dynamic> toJson() => {
'product_id': productId,
'variant_name': variantName,
'warehouse_id': warehouseId,
'productDealerPrice': productDealerPrice,
'productPurchasePrice': productPurchasePrice,
'productSalePrice': productSalePrice,
'productWholeSalePrice': productWholeSalePrice,
'quantities': quantities,
'batch_no': batchNumber,
'profit_percent': profitPercent,
'expire_date': expireDate,
'mfg_date': mfgDate,
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,332 @@
import 'dart:io';
import 'package:excel/excel.dart' as e;
import 'package:file_selector/file_selector.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mobile_pos/Provider/product_provider.dart';
import 'package:mobile_pos/Screens/Products/add%20product/add_product.dart';
import 'package:mobile_pos/constant.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../../GlobalComponents/glonal_popup.dart';
import '../../../Provider/add_to_cart_purchase.dart';
import '../../../http_client/custome_http_client.dart';
import '../../../service/check_user_role_permission_provider.dart';
import '../../Products/add product/modle/create_product_model.dart';
import '../Repo/purchase_repo.dart';
class BulkPurchaseUploader extends ConsumerStatefulWidget {
const BulkPurchaseUploader({super.key});
@override
ConsumerState<BulkPurchaseUploader> createState() => _BulkPurchaseUploaderState();
}
class _BulkPurchaseUploaderState extends ConsumerState<BulkPurchaseUploader> {
String? filePat;
File? file;
String getFileExtension(String fileName) {
return fileName.split('/').last;
}
Future<void> createExcelFile() async {
if (!await Permission.storage.request().isDenied) {
EasyLoading.showError('Storage permission is required to create Excel file!');
return;
}
EasyLoading.show();
final List<e.CellValue> excelData = [
e.TextCellValue('SL'),
e.TextCellValue('Product Code*'),
e.TextCellValue('Purchase Quantity*'),
e.TextCellValue('Purchase Price'),
e.TextCellValue('Profit Percent %'),
e.TextCellValue('Sale Price'),
e.TextCellValue('Wholesale Price'),
e.TextCellValue('Dealer Price'),
e.TextCellValue('Batch No'),
e.TextCellValue('Mfg Date'),
e.TextCellValue('Expire Date'),
];
e.CellStyle cellStyle = e.CellStyle(
bold: true,
textWrapping: e.TextWrapping.WrapText,
rotation: 0,
);
var excel = e.Excel.createExcel();
var sheet = excel['Sheet1'];
sheet.appendRow(excelData);
for (int i = 0; i < excelData.length; i++) {
var cell = sheet.cell(e.CellIndex.indexByColumnRow(columnIndex: i, rowIndex: 0));
cell.cellStyle = cellStyle;
}
const downloadsFolderPath = '/storage/emulated/0/Download/';
Directory dir = Directory(downloadsFolderPath);
final file = File('${dir.path}/${appsName}_bulk_purchase_upload.xlsx');
if (await file.exists()) {
EasyLoading.showSuccess('The Excel file has already been downloaded');
} else {
await file.writeAsBytes(excel.encode()!);
EasyLoading.showSuccess('Downloaded successfully in download folder');
}
}
@override
Widget build(BuildContext context) {
final permissionService = PermissionService(ref);
return GlobalPopup(
child: Scaffold(
backgroundColor: kWhite,
appBar: AppBar(
title: const Text('Excel Uploader'),
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Visibility(
visible: file != null,
child: Padding(
padding: const EdgeInsets.only(bottom: 20),
child: Card(
child: ListTile(
leading: Container(
height: 40,
width: 40,
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: const BorderRadius.all(Radius.circular(10)),
),
child: const Image(image: AssetImage('images/excel.png'))),
title: Text(
getFileExtension(file?.path ?? ''),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
trailing: GestureDetector(
onTap: () {
setState(() {
file = null;
});
},
child: const Text('Remove')))),
),
),
Visibility(
visible: file == null,
child: const Padding(
padding: EdgeInsets.only(bottom: 20),
child: Image(
height: 100,
width: 100,
image: AssetImage('images/file-upload.png'),
)),
),
ElevatedButton(
style: const ButtonStyle(backgroundColor: WidgetStatePropertyAll(kMainColor)),
onPressed: () async {
if (!permissionService.hasPermission(Permit.bulkUploadsCreate.value)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Colors.red,
content: Text('You do not have permission to upload bulk.'),
),
);
return;
}
if (file == null) {
await pickAndUploadFile(ref: ref);
} else {
EasyLoading.show(status: 'Uploading...');
await uploadProducts(ref: ref, file: file!, context: context);
EasyLoading.dismiss();
}
},
child: Text(file == null ? 'Pick and Upload File' : 'Upload', style: const TextStyle(color: Colors.white)),
),
TextButton(
onPressed: () async {
if (!permissionService.hasPermission(Permit.bulkUploadsRead.value)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Colors.red,
content: Text('You do not have permission to download file.'),
),
);
return;
}
await createExcelFile();
},
child: const Text('Download Excel Format'),
),
],
),
),
),
),
);
}
///
Future<void> pickAndUploadFile({required WidgetRef ref}) async {
const XTypeGroup typeGroup = XTypeGroup(
label: 'Excel Files',
extensions: ['xlsx'],
);
final XFile? fileResult = await openFile(acceptedTypeGroups: [typeGroup]);
if (fileResult != null) {
final File files = File(fileResult.path);
setState(() {
file = files;
});
} else {
print("No file selected");
}
}
Future<void> uploadProducts({
required File file,
required WidgetRef ref,
required BuildContext context,
}) async {
try {
final purchaseCart = ref.watch(cartNotifierPurchaseNew);
e.Excel excel = e.Excel.decodeBytes(file.readAsBytesSync());
var sheet = excel.sheets.keys.first;
var table = excel.tables[sheet]!;
for (var row in table.rows) {
CartProductModelPurchase? data = await createProductModelFromExcelData(row: row, ref: ref);
if (data != null) purchaseCart.addToCartRiverPod(cartItem: data, isVariation: data.productType == ProductType.variant.name);
}
Future.delayed(const Duration(seconds: 1), () {
EasyLoading.showSuccess('Upload Done');
int count = 0;
Navigator.popUntil(context, (route) {
return count++ == 1;
});
});
} catch (e) {
EasyLoading.showError(e.toString());
return;
}
}
Future<CartProductModelPurchase?> createProductModelFromExcelData({required List<e.Data?> row, required WidgetRef ref}) async {
Future<CartProductModelPurchase?> getProductFromDatabase({required WidgetRef ref, required String givenProductCode}) async {
final products = ref.watch(productProvider);
CartProductModelPurchase? cartProductModel;
// Wait for the category data to load
await products.when(
data: (product) async {
for (var element in product) {
if (element.productCode?.toLowerCase().trim() == givenProductCode.toLowerCase().trim()) {
cartProductModel = CartProductModelPurchase(
productId: element.id ?? 0,
vatRate: element.vat?.rate ?? 0,
productName: element.productName ?? '',
vatAmount: element.vatAmount ?? 0,
vatType: element.vatType ?? '',
productWholeSalePrice: 0,
productDealerPrice: 0,
productPurchasePrice: 0,
productSalePrice: 0,
productType: element.productType ?? 'single',
quantities: 0,
stock: 0,
brandName: '',
profitPercent: 0,
mfgDate: '',
expireDate: '',
batchNumber: '',
);
return cartProductModel;
}
}
},
error: (error, stackTrace) {},
loading: () {},
);
return cartProductModel;
}
CartProductModelPurchase? productModel;
// Loop through the row data
for (var element in row) {
if (element?.rowIndex == 0) {
// Skip header row
return null;
}
switch (element?.columnIndex) {
case 1: // Product code
if (element?.value == null) return null;
productModel = await getProductFromDatabase(ref: ref, givenProductCode: element?.value.toString() ?? '');
break;
case 2: // Product quantity
if (element?.value == null) return null;
productModel?.quantities = num.tryParse(element?.value.toString() ?? '0');
break;
case 3: // purchase price
productModel?.productPurchasePrice = num.tryParse(element?.value.toString() ?? '') ?? 0;
break;
case 4: // profit percent
productModel?.profitPercent = num.tryParse(element?.value.toString() ?? '') ?? 0;
break;
case 5: // sales price
productModel?.productSalePrice = num.tryParse(element?.value.toString() ?? '') ?? 0;
break;
case 6: // wholesale price
productModel?.productWholeSalePrice = num.tryParse(element?.value.toString() ?? '') ?? 0;
break;
case 7: //dealer price
if (element?.value != null) {
productModel?.productDealerPrice = num.tryParse(element?.value.toString() ?? '') ?? 0;
}
break;
case 8: // Batch (optional)
if (element?.value != null) {
productModel?.batchNumber = element?.value.toString() ?? '';
}
break;
case 9: // mgf date (optional)
if (element?.value != null) {
productModel?.mfgDate = element?.value.toString() ?? '';
}
break;
case 10: // expire date (optional)
if (element?.value != null) {
productModel?.expireDate = element?.value.toString() ?? '';
}
break;
}
}
// Return null if any of the required fields are missing
if (productModel?.productName == null || productModel?.quantities == null) {
return null;
}
return productModel;
}
}

View File

@@ -0,0 +1,203 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:iconly/iconly.dart';
import 'package:mobile_pos/Const/api_config.dart';
import 'package:mobile_pos/Provider/add_to_cart_purchase.dart';
import 'package:mobile_pos/Provider/profile_provider.dart';
import 'package:mobile_pos/Screens/Customers/add_customer.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import 'package:nb_utils/nb_utils.dart';
import '../../GlobalComponents/glonal_popup.dart';
import '../../constant.dart';
import '../../currency.dart';
import '../../widgets/empty_widget/_empty_widget.dart';
import '../Customers/Provider/customer_provider.dart';
import 'add_and_edit_purchase.dart';
class PurchaseContacts extends StatefulWidget {
const PurchaseContacts({super.key});
@override
State<PurchaseContacts> createState() => _PurchaseContactsState();
}
class _PurchaseContactsState extends State<PurchaseContacts> {
Color color = Colors.black26;
String searchCustomer = '';
@override
Widget build(BuildContext context) {
return Consumer(builder: (context, ref, __) {
final _theme = Theme.of(context);
final providerData = ref.watch(partiesProvider);
final businessInfo = ref.watch(businessInfoProvider);
return businessInfo.when(data: (details) {
return GlobalPopup(
child: Scaffold(
backgroundColor: kWhite,
resizeToAvoidBottomInset: true,
appBar: AppBar(
backgroundColor: Colors.white,
title: Text(
lang.S.of(context).chooseSupplier,
),
centerTitle: true,
iconTheme: const IconThemeData(color: Colors.black),
elevation: 0.0,
),
body: SingleChildScrollView(
child: providerData.when(data: (customer) {
return customer.isNotEmpty
? Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Column(
children: [
TextFormField(
keyboardType: TextInputType.name,
decoration: InputDecoration(
border: const OutlineInputBorder(),
hintText: lang.S.of(context).search,
prefixIcon: Icon(
Icons.search,
color: kGreyTextColor.withOpacity(0.5),
),
),
onChanged: (value) {
setState(() {
searchCustomer = value.toLowerCase().trim();
});
},
),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: customer.length,
itemBuilder: (_, index) {
customer[index].type == 'Supplier' ? color = const Color(0xFFA569BD) : Colors.white;
return customer[index].name!.toLowerCase().trim().contains(searchCustomer) &&
customer[index].type!.contains('Supplier')
? ListTile(
contentPadding: EdgeInsets.zero,
onTap: () async {
ref.refresh(cartNotifierPurchaseNew);
AddAndUpdatePurchaseScreen(customerModel: customer[index]).launch(context);
},
leading: customer[index].image != null
? Container(
height: 40,
width: 40,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.grey.shade50, width: 0.3),
image: DecorationImage(
image: NetworkImage(
'${APIConfig.domain}${customer[index].image}',
),
fit: BoxFit.cover),
),
)
: CircleAvatarWidget(name: customer[index].name),
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
customer[index].name ?? '',
maxLines: 1,
textAlign: TextAlign.start,
overflow: TextOverflow.ellipsis,
style: _theme.textTheme.bodyMedium?.copyWith(
color: Colors.black,
fontSize: 16.0,
),
),
),
const SizedBox(width: 4),
Text(
'$currency${customer[index].due}',
style: _theme.textTheme.bodyMedium?.copyWith(
fontSize: 16.0,
),
),
],
),
subtitle: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
customer[index].type == 'Supplier'
? lang.S.of(context).supplier
: customer[index].type ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: _theme.textTheme.bodyMedium?.copyWith(
color: color,
fontSize: 14.0,
),
),
),
const SizedBox(width: 4),
Text(
customer[index].due != null && customer[index].due != 0
? lang.S.of(context).due
: lang.S.of(context).noDue,
style: _theme.textTheme.bodyMedium?.copyWith(
color: customer[index].due != null && customer[index].due != 0
? const Color(0xFFff5f00)
: const Color(0xff7B787B),
fontSize: 14.0,
),
),
],
),
trailing: const Icon(
IconlyLight.arrow_right_2,
size: 18,
),
)
: const SizedBox.shrink();
},
),
],
),
)
: Center(
child: EmptyWidget(
message: TextSpan(
text: lang.S.of(context).noSupplier,
),
),
);
}, error: (e, stack) {
return Text(e.toString());
}, loading: () {
return const Center(child: CircularProgressIndicator());
}),
),
floatingActionButton: FloatingActionButton(
backgroundColor: kMainColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100),
),
child: const Icon(
Icons.add,
color: kWhite,
),
onPressed: () async {
const AddParty().launch(context);
}),
),
);
}, error: (e, stack) {
return Text(e.toString());
}, loading: () {
return const Center(
child: CircularProgressIndicator(),
);
});
});
}
}

View File

@@ -0,0 +1,222 @@
// import 'package:flutter/material.dart';
// import 'package:mobile_pos/generated/l10n.dart' as lang;
// import 'package:nb_utils/nb_utils.dart';
//
// import '../../GlobalComponents/glonal_popup.dart';
// import '../../constant.dart';
//
// class PurchaseList extends StatefulWidget {
// const PurchaseList({Key? key}) : super(key: key);
//
// @override
// // ignore: library_private_types_in_public_api
// _PurchaseListState createState() => _PurchaseListState();
// }
//
// class _PurchaseListState extends State<PurchaseList> {
// final dateController = TextEditingController();
//
// String dropdownValue = 'Last 30 Days';
//
// DropdownButton<String> getCategory() {
// List<String> dropDownItems = [
// 'Last 7 Days',
// 'Last 30 Days',
// 'Current year',
// 'Last Year',
// ];
// return DropdownButton(
// items: dropDownItems.map<DropdownMenuItem<String>>((String value) {
// return DropdownMenuItem<String>(
// value: value,
// child: Text(value),
// );
// }).toList(),
// value: dropdownValue,
// onChanged: (value) {
// setState(() {
// dropdownValue = value!;
// });
// },
// );
// }
//
// @override
// void dispose() {
// dateController.dispose();
// super.dispose();
// }
//
// @override
// Widget build(BuildContext context) {
// return GlobalPopup(
// child: Scaffold(
// appBar: AppBar(
// title: Text(
// lang.S.of(context).purchaseList,
// //'Purchase List',
// ),
// iconTheme: const IconThemeData(color: Colors.black),
// centerTitle: true,
// backgroundColor: Colors.white,
// elevation: 0.0,
// ),
// body: Padding(
// padding: const EdgeInsets.all(10.0),
// child: Column(
// children: [
// Column(
// children: [
// const SizedBox(
// height: 10.0,
// ),
// Row(
// children: [
// Expanded(
// child: Padding(
// padding: const EdgeInsets.all(4.0),
// child: Container(
// height: 60.0,
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(5.0),
// border: Border.all(color: kGreyTextColor),
// ),
// child: Center(child: getCategory()),
// ),
// ),
// ),
// Expanded(
// child: Padding(
// padding: const EdgeInsets.all(4.0),
// child: AppTextField(
// textFieldType: TextFieldType.NAME,
// readOnly: true,
// onTap: () async {
// var date = await showDatePicker(context: context, initialDate: DateTime.now(), firstDate: DateTime(1900), lastDate: DateTime(2100));
// dateController.text = date.toString().substring(0, 10);
// },
// controller: dateController,
// decoration: InputDecoration(
// border: OutlineInputBorder(),
// floatingLabelBehavior: FloatingLabelBehavior.always,
// //labelText: 'Start Date',
// labelText: lang.S.of(context).startDate,
// //hintText: 'Pick Start Date'
// hintText: lang.S.of(context).pickStartDate),
// ),
// ),
// ),
// Expanded(
// child: Padding(
// padding: const EdgeInsets.all(4.0),
// child: AppTextField(
// textFieldType: TextFieldType.OTHER,
// readOnly: true,
// onTap: () async {
// var date = await showDatePicker(context: context, initialDate: DateTime.now(), firstDate: DateTime(1900), lastDate: DateTime(2100));
// dateController.text = date.toString().substring(0, 10);
// },
// controller: dateController,
// decoration: InputDecoration(
// border: OutlineInputBorder(),
// floatingLabelBehavior: FloatingLabelBehavior.always,
// //labelText: 'End Date',
// labelText: lang.S.of(context).endDate,
// //hintText: 'Pick End Date'
// hintText: lang.S.of(context).pickEndDate),
// ),
// ),
// ),
// ],
// ),
// const SizedBox(
// height: 10.0,
// ),
// SingleChildScrollView(
// scrollDirection: Axis.vertical,
// child: DataTable(
// columnSpacing: 80,
// horizontalMargin: 0,
// headingRowColor: MaterialStateColor.resolveWith((states) => kDarkWhite),
// columns: <DataColumn>[
// DataColumn(
// label: Text(
// // 'Name',
// lang.S.of(context).name),
// ),
// DataColumn(
// label: Text(lang.S.of(context).quantity
// //'Quantity',
// ),
// ),
// DataColumn(
// label: Text(lang.S.of(context).amount
// //'Amount',
// ),
// ),
// ],
// rows: <DataRow>[
// DataRow(
// cells: <DataCell>[
// DataCell(
// Row(
// children: [
// Container(
// padding: const EdgeInsets.only(right: 3.0),
// height: 30.0,
// width: 30.0,
// child: const CircleAvatar(
// backgroundImage: AssetImage('images/profile.png'),
// ),
// ),
// Text(
// 'Riead',
// textAlign: TextAlign.start,
// style: Theme.of(context).textTheme.bodyLarge,
// ),
// ],
// ),
// ),
// const DataCell(
// Text('2'),
// ),
// const DataCell(
// Text('25'),
// ),
// ],
// ),
// ],
// ),
// ),
// ],
// ),
// const Spacer(),
// DataTable(
// columnSpacing: 120,
// headingRowColor: MaterialStateColor.resolveWith((states) => kDarkWhite),
// columns: <DataColumn>[
// DataColumn(
// label: Text(lang.S.of(context).totall
// //'Total:',
// ),
// ),
// const DataColumn(
// label: Text(
// '8',
// ),
// ),
// const DataColumn(
// label: Text(
// '50',
// ),
// ),
// ],
// rows: const [],
// ),
// ],
// ),
// ),
// ),
// );
// }
// }

View File

@@ -0,0 +1,617 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:iconly/iconly.dart';
import 'package:intl/intl.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
// Adjust imports to match your project structure
import '../../Provider/add_to_cart_purchase.dart';
import '../../Provider/product_provider.dart';
import '../../constant.dart';
import '../Products/Model/product_model.dart';
import '../Products/Repo/product_repo.dart';
import '../../service/check_user_role_permission_provider.dart';
import '../Products/add product/modle/create_product_model.dart';
import '../warehouse/warehouse_model/warehouse_list_model.dart';
import '../warehouse/warehouse_provider/warehouse_provider.dart';
import 'Repo/purchase_repo.dart';
Future<void> addProductInPurchaseCartButtomSheet({
required BuildContext context,
required CartProductModelPurchase product,
required WidgetRef ref,
required bool fromUpdate,
required int index,
required bool fromStock,
required List<Stock> stocks,
}) {
final theme = Theme.of(context);
final permissionService = PermissionService(ref);
final decimalInputFormatter = [FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,2}'))];
final _formKey = GlobalKey<FormState>();
// Controllers
final TextEditingController productStockController = TextEditingController(text: product.quantities.toString());
final TextEditingController salePriceController = TextEditingController(text: '${product.productSalePrice}');
final TextEditingController purchaseExclusivePriceController = TextEditingController(
text: product.vatType == 'exclusive'
? '${product.productPurchasePrice}'
: '${((product.productPurchasePrice ?? 0) / (1 + product.vatRate / 100))}');
final TextEditingController profitMarginController = TextEditingController(text: '${product.profitPercent}');
final TextEditingController purchaseInclusivePriceController = TextEditingController();
final TextEditingController wholeSalePriceController =
TextEditingController(text: '${product.productWholeSalePrice}');
final TextEditingController dealerPriceController = TextEditingController(text: '${product.productDealerPrice}');
final TextEditingController expireDateController = TextEditingController(text: product.expireDate ?? '');
final TextEditingController manufactureDateController = TextEditingController(text: product.mfgDate ?? '');
final TextEditingController productBatchNumberController = TextEditingController(text: product.batchNumber ?? '');
// Initialization variables
bool isCalculated = false;
// These will be managed inside the StatefulBuilder
num? selectedWarehouseId = product.warehouseId;
Stock? selectedStock;
return showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
isScrollControlled: true,
builder: (context) {
return Consumer(
builder: (context, ref, child) {
final warehouseAsyncValue = ref.watch(fetchWarehouseListProvider);
return warehouseAsyncValue.when(
loading: () => Padding(
padding: const EdgeInsets.all(30.0),
child: const Center(child: CircularProgressIndicator()),
),
error: (err, stack) => Padding(
padding: const EdgeInsets.all(30.0),
child: Center(child: Text('Error loading warehouses: $err')),
),
data: (warehouseModel) {
final warehouseList = warehouseModel.data ?? [];
return StatefulBuilder(
builder: (context, setState) {
// --- PRICE CALCULATION FUNCTION ---
void calculatePurchaseAndMrp({String? from}) {
num purchaseExc = 0;
num purchaseInc = 0;
num profitMargin = num.tryParse(profitMarginController.text) ?? 0;
num salePrice = 0;
if (from == 'purchase_inc') {
purchaseExc = (num.tryParse(purchaseInclusivePriceController.text) ?? 0) /
(1 + (product.vatRate ?? 0) / 100);
purchaseExclusivePriceController.text = purchaseExc.toStringAsFixed(2);
} else {
purchaseExc = num.tryParse(purchaseExclusivePriceController.text) ?? 0;
purchaseInc = purchaseExc + (purchaseExc * (product.vatRate ?? 0) / 100);
purchaseInclusivePriceController.text = purchaseInc.toStringAsFixed(2);
}
purchaseInc = num.tryParse(purchaseInclusivePriceController.text) ?? 0;
if (from == 'mrp') {
salePrice = num.tryParse(salePriceController.text) ?? 0;
num costPrice = (product.vatType.toLowerCase() == 'exclusive' ? purchaseExc : purchaseInc);
if (costPrice > 0) {
profitMargin = ((salePrice - costPrice) / costPrice) * 100;
profitMarginController.text = profitMargin.toStringAsFixed(2);
}
} else {
salePrice = (product.vatType.toLowerCase() == 'exclusive')
? purchaseExc + (purchaseExc * profitMargin / 100)
: purchaseInc + (purchaseInc * profitMargin / 100);
salePriceController.text = salePrice.toStringAsFixed(2);
}
// No setState needed here if called inside setState,
// but needed if called from TextField onChanged outside build
}
// Helper to populate fields from a Stock object
void populateFieldsFromStock(Stock stock) {
productBatchNumberController.text = stock.batchNo ?? '';
// Update Prices
purchaseExclusivePriceController.text = stock.productPurchasePrice?.toString() ?? '0';
salePriceController.text = stock.productSalePrice?.toString() ?? '0';
wholeSalePriceController.text = stock.productWholeSalePrice?.toString() ?? '0';
dealerPriceController.text = stock.productDealerPrice?.toString() ?? '0';
// Update Dates
manufactureDateController.text = stock.mfgDate ?? '';
expireDateController.text = stock.expireDate ?? '';
// Recalculate Inclusive Price based on new Exclusive Price
num purchaseExc = stock.productPurchasePrice ?? 0;
num purchaseInc = purchaseExc + (purchaseExc * (product.vatRate ?? 0) / 100);
purchaseInclusivePriceController.text = purchaseInc.toStringAsFixed(2);
// Recalculate Margin based on new Sale Price
num salePrice = stock.productSalePrice ?? 0;
num costPrice = (product.vatType.toLowerCase() == 'exclusive' ? purchaseExc : purchaseInc);
if (costPrice > 0) {
num profitMargin = ((salePrice - costPrice) / costPrice) * 100;
profitMarginController.text = profitMargin.toStringAsFixed(2);
} else {
profitMarginController.text = '0';
}
}
// --- 1. INITIALIZATION LOGIC (Runs once) ---
if (!isCalculated) {
// A. Calculate Initial Inclusive Price
num purchaseExc = num.tryParse(purchaseExclusivePriceController.text) ?? 0;
num purchaseInc = purchaseExc + (purchaseExc * (product.vatRate ?? 0) / 100);
purchaseInclusivePriceController.text = purchaseInc.toStringAsFixed(2);
// B. Auto-Select Stock based on matching Batch Number
try {
if (product.batchNumber != null && product.batchNumber!.isNotEmpty) {
selectedStock = stocks.firstWhere(
(element) => element.batchNo == product.batchNumber,
);
// Optional: If you want to force update fields on load from the matched stock:
// if(selectedStock != null) populateFieldsFromStock(selectedStock!);
}
} catch (e) {
selectedStock = null;
}
// C. Auto-Select Warehouse
if (selectedStock != null) {
selectedWarehouseId = selectedStock!.warehouseId;
} else {
selectedWarehouseId = product.warehouseId;
}
isCalculated = true;
}
// --- 3. SAFETY CHECK ---
if (selectedStock != null) {
bool existsInFilter = stocks.any((element) => element.id == selectedStock!.id);
if (!existsInFilter) {
selectedStock = null;
}
}
return Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(lang.S.of(context).addVariantDetails, style: theme.textTheme.titleMedium),
IconButton(onPressed: () => Navigator.pop(context), icon: Icon(Icons.close)),
],
),
Divider(color: kBorderColor),
const SizedBox(height: 12),
// ---------------- WAREHOUSE & STOCK SECTION ----------------
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Warehouse Dropdown
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(lang.S.of(context).warehouse,
style: TextStyle(fontSize: 12, color: Colors.grey)),
SizedBox(height: 5),
DropdownButtonFormField<num>(
value: selectedWarehouseId,
isExpanded: true,
decoration: kInputDecoration.copyWith(
hintText: lang.S.of(context).select,
contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 0),
),
items: warehouseList.map((WarehouseData warehouse) {
return DropdownMenuItem<num>(
value: warehouse.id,
child: Text(
warehouse.name ?? 'Unknown',
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 14),
),
);
}).toList(),
onChanged: (value) {
setState(() {
selectedWarehouseId = value;
});
},
),
],
),
),
// Stock Dropdown (Auto-Populate Logic Here)
if (product.productType != 'single') ...[
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(lang.S.of(context).stockOrVariant,
style: TextStyle(fontSize: 12, color: Colors.grey)),
SizedBox(height: 5),
DropdownButtonFormField<Stock>(
value: selectedStock,
isExpanded: true,
decoration: kInputDecoration.copyWith(
hintText: lang.S.of(context).selectStock,
contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 0),
),
items: stocks.map((stock) {
String displayName = stock.batchNo ?? lang.S.of(context).noBatch;
if (stock.variantName != null && stock.variantName!.isNotEmpty) {
displayName = "${stock.variantName} ($displayName)";
}
return DropdownMenuItem<Stock>(
value: stock,
child: Text(
displayName,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 14),
),
);
}).toList(),
onChanged: (Stock? value) {
setState(() {
selectedStock = value;
if (value != null) {
// Call helper to update UI controllers
populateFieldsFromStock(value);
}
});
},
),
],
),
),
],
],
),
const SizedBox(height: 16),
// ---------------- END WAREHOUSE & STOCK SECTION ----------------
Row(
spacing: 12,
children: [
if (product.productType == ProductType.variant.name)
Expanded(
child: TextFormField(
controller: productBatchNumberController,
decoration: kInputDecoration.copyWith(
labelText: lang.S.of(context).batchNo,
hintText: lang.S.of(context).enterBatchNo,
),
),
),
Expanded(
child: TextFormField(
controller: productStockController,
inputFormatters: decimalInputFormatter,
keyboardType: TextInputType.number,
decoration: kInputDecoration.copyWith(
labelText: lang.S.of(context).quantity,
hintText: lang.S.of(context).enterQuantity,
),
validator: (value) {
if ((num.tryParse(value ?? '') ?? 0) <= 0) {
return lang.S.of(context).purchaseQuantityRequired;
}
return null;
},
),
),
],
),
const SizedBox(height: 16),
if (permissionService.hasPermission(Permit.purchasesPriceView.value)) ...{
Row(
children: [
Expanded(
child: TextFormField(
controller: purchaseExclusivePriceController,
onChanged: (value) {
setState(() {
calculatePurchaseAndMrp();
});
},
inputFormatters: decimalInputFormatter,
keyboardType: TextInputType.number,
decoration: kInputDecoration.copyWith(
labelText: lang.S.of(context).purchaseEx,
hintText: lang.S.of(context).enterPurchasePrice,
),
validator: (value) {
if ((num.tryParse(value ?? '') ?? 0) <= 0) {
return lang.S.of(context).purchaseExReq;
}
return null;
},
),
),
const SizedBox(width: 12),
Expanded(
child: TextFormField(
controller: purchaseInclusivePriceController,
onChanged: (value) {
setState(() {
calculatePurchaseAndMrp(from: "purchase_inc");
});
},
inputFormatters: decimalInputFormatter,
keyboardType: TextInputType.number,
decoration: kInputDecoration.copyWith(
labelText: lang.S.of(context).purchaseIn,
hintText: lang.S.of(context).enterSaltingPrice,
),
validator: (value) {
if ((num.tryParse(value ?? '') ?? 0) <= 0) {
return lang.S.of(context).purchaseInReq;
}
return null;
},
),
),
],
),
const SizedBox(height: 16),
},
Row(
children: [
if (permissionService.hasPermission(Permit.purchasesPriceView.value)) ...{
Expanded(
child: TextFormField(
controller: profitMarginController,
onChanged: (value) {
setState(() {
calculatePurchaseAndMrp();
});
},
inputFormatters: decimalInputFormatter,
keyboardType: TextInputType.number,
decoration: kInputDecoration.copyWith(
labelText: lang.S.of(context).profitMargin,
hintText: lang.S.of(context).enterPurchasePrice,
),
),
),
const SizedBox(width: 12),
},
Expanded(
child: TextFormField(
controller: salePriceController,
onChanged: (value) {
setState(() {
calculatePurchaseAndMrp(from: 'mrp');
});
},
inputFormatters: decimalInputFormatter,
keyboardType: TextInputType.number,
decoration: kInputDecoration.copyWith(
labelText: lang.S.of(context).mrp,
hintText: lang.S.of(context).enterSaltingPrice,
),
validator: (value) {
if ((num.tryParse(value ?? '') ?? 0) <= 0) {
return lang.S.of(context).saleReq;
}
return null;
},
),
),
],
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: TextFormField(
controller: wholeSalePriceController,
inputFormatters: decimalInputFormatter,
keyboardType: TextInputType.number,
decoration: kInputDecoration.copyWith(
labelText: lang.S.of(context).wholeSalePrice,
hintText: lang.S.of(context).enterWholesalePrice,
),
),
),
const SizedBox(width: 12),
Expanded(
child: TextFormField(
controller: dealerPriceController,
inputFormatters: decimalInputFormatter,
keyboardType: TextInputType.number,
decoration: kInputDecoration.copyWith(
labelText: lang.S.of(context).dealerPrice,
hintText: lang.S.of(context).enterDealerPrice,
),
),
),
],
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: TextFormField(
controller: manufactureDateController,
readOnly: true,
decoration: kInputDecoration.copyWith(
labelText: lang.S.of(context).manufactureDate,
hintText: lang.S.of(context).selectDate,
suffixIcon: IconButton(
icon: Icon(IconlyLight.calendar),
onPressed: () async {
final picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2015),
lastDate: DateTime(2101),
);
if (picked != null) {
setState(() {
manufactureDateController.text = DateFormat.yMd().format(picked);
});
}
},
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: TextFormField(
controller: expireDateController,
readOnly: true,
decoration: kInputDecoration.copyWith(
labelText: lang.S.of(context).expDate,
hintText: lang.S.of(context).selectDate,
suffixIcon: IconButton(
icon: Icon(IconlyLight.calendar),
onPressed: () async {
final picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2015),
lastDate: DateTime(2101),
);
if (picked != null) {
setState(() {
expireDateController.text = DateFormat.yMd().format(picked);
});
}
},
),
),
),
),
],
),
const SizedBox(height: 24),
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () => Navigator.pop(context),
style: ButtonStyle(
side: WidgetStatePropertyAll(BorderSide(color: Color(0xffF68A3D)))),
child:
Text(lang.S.of(context).cancel, style: TextStyle(color: Color(0xffF68A3D))),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
final cartProduct = CartProductModelPurchase(
warehouseId: selectedWarehouseId,
productId: product.productId ?? 0,
// SAVE SELECTED STOCK ID OR ORIGINAL STOCK ID IF SINGLE
variantName: product.productType == 'single'
? (product.variantName)
: (selectedStock?.variantName ?? product.variantName),
brandName: product.brandName ?? '',
productName: product.productName ?? '',
productType: product.productType,
vatAmount: product.vatAmount,
vatRate: product.vatRate,
vatType: product.vatType,
batchNumber: productBatchNumberController.text,
productDealerPrice: num.tryParse(dealerPriceController.text),
productPurchasePrice: num.tryParse(product.vatType == 'exclusive'
? purchaseExclusivePriceController.text
: purchaseInclusivePriceController.text),
productSalePrice: num.tryParse(salePriceController.text),
productWholeSalePrice: num.tryParse(wholeSalePriceController.text),
quantities: num.tryParse(productStockController.text),
expireDate: dateFormateChange(date: expireDateController.text),
mfgDate: dateFormateChange(date: manufactureDateController.text),
profitPercent: num.tryParse(profitMarginController.text));
if (fromStock) {
ProductRepo productRepo = ProductRepo();
bool success = await productRepo.updateVariation(data: cartProduct);
if (success) {
ref.refresh(productProvider);
ref.refresh(fetchProductDetails(product.productId.toString()));
Navigator.pop(context);
}
} else if (fromUpdate) {
ref
.watch(cartNotifierPurchaseNew)
.updateProduct(index: index, newProduct: cartProduct);
Navigator.pop(context);
} else {
ref.watch(cartNotifierPurchaseNew).addToCartRiverPod(
cartItem: cartProduct,
isVariation: product.productType == ProductType.variant.name);
int count = 0;
Navigator.popUntil(context, (route) {
return count++ == 2;
});
}
}
},
child: Text(lang.S.of(context).saveVariant),
),
),
],
),
],
),
),
),
),
);
},
);
},
);
},
);
},
);
}
// Helper Function
String dateFormateChange({required String? date}) {
if (date == null || date.trim().isEmpty) return '';
try {
DateTime parsed;
if (date.contains('-')) {
parsed = DateTime.parse(date);
} else {
parsed = DateFormat("M/d/yyyy").parse(date);
}
return DateFormat("yyyy-MM-dd").format(parsed);
} catch (e) {
print('Failed to format date: $date$e');
return '';
}
}

View File

@@ -0,0 +1,314 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mobile_pos/Const/api_config.dart';
import 'package:mobile_pos/Provider/product_provider.dart';
import 'package:mobile_pos/Screens/Customers/Model/parties_model.dart';
import 'package:mobile_pos/Screens/Products/add%20product/add_product.dart';
import 'package:mobile_pos/Screens/Purchase/Repo/purchase_repo.dart';
import 'package:mobile_pos/Screens/Purchase/purchase_product_buttom_sheet.dart';
import 'package:mobile_pos/constant.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import '../../GlobalComponents/bar_code_scaner_widget.dart';
import '../../GlobalComponents/glonal_popup.dart';
import '../../core/theme/_app_colors.dart';
import '../../widgets/empty_widget/_empty_widget.dart';
import '../Products/Model/product_model.dart';
import '../Products/add product/modle/create_product_model.dart';
class PurchaseProducts extends StatefulWidget {
PurchaseProducts({super.key, this.customerModel});
Party? customerModel;
@override
State<PurchaseProducts> createState() => _PurchaseProductsState();
}
class _PurchaseProductsState extends State<PurchaseProducts> {
String productCode = '0000';
TextEditingController codeController = TextEditingController();
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Consumer(builder: (context, ref, __) {
final _theme = Theme.of(context);
final productList = ref.watch(productProvider);
return GlobalPopup(
child: Scaffold(
backgroundColor: kWhite,
appBar: AppBar(
title: Text(
lang.S.of(context).productList,
),
iconTheme: const IconThemeData(color: Colors.black),
centerTitle: true,
backgroundColor: Colors.white,
elevation: 0.0,
),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 10),
child: Row(
children: [
Expanded(
flex: 3,
child: TextFormField(
controller: codeController,
keyboardType: TextInputType.name,
onChanged: (value) {
setState(() {
productCode = value;
});
},
decoration: InputDecoration(
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: lang.S.of(context).productCode,
hintText: productCode == '0000' || productCode == '-1' ? lang.S.of(context).scanCode : productCode,
border: const OutlineInputBorder(),
),
),
),
const SizedBox(width: 10),
Expanded(
flex: 1,
child: GestureDetector(
onTap: () async {
showDialog(
context: context,
builder: (context) => BarcodeScannerWidget(
onBarcodeFound: (String code) {
setState(() {
productCode = code;
codeController.text = productCode;
});
},
),
);
},
child: const BarCodeButton(),
),
),
],
),
),
productList.when(data: (products) {
final filteredProducts = products.where((element) => element.productType?.toLowerCase() != 'combo').toList();
// CHANGE END
return ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
// Use filteredProducts.length instead of products.length
itemCount: filteredProducts.length,
itemBuilder: (_, i) {
// Replace 'products[i]' with 'filteredProducts[i]' everywhere below
return Visibility(
visible: ((filteredProducts[i].productCode == productCode || productCode == '0000' || productCode == '-1')) ||
filteredProducts[i].productName!.toLowerCase().contains(productCode.toLowerCase()),
child: ListTile(
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
contentPadding: EdgeInsets.zero,
leading: filteredProducts[i].productPicture == null
? CircleAvatarWidget(
name: filteredProducts[i].productName,
size: const Size(50, 50),
)
: Container(
height: 50,
width: 50,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: NetworkImage(
'${APIConfig.domain}${filteredProducts[i].productPicture!}',
),
fit: BoxFit.cover,
),
),
),
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Text(
filteredProducts[i].productName.toString(),
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: _theme.textTheme.bodyMedium?.copyWith(
fontSize: 16,
fontWeight: FontWeight.w400,
),
),
),
const SizedBox(width: 4),
Text(
lang.S.of(context).stock,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: _theme.textTheme.bodyMedium?.copyWith(
fontSize: 16,
fontWeight: FontWeight.w400,
),
),
],
),
subtitle: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Text(
filteredProducts[i].brand?.brandName ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: _theme.textTheme.bodyMedium?.copyWith(
fontSize: 14,
fontWeight: FontWeight.w400,
color: DAppColors.kSecondary,
),
),
),
const SizedBox(width: 4),
Text(
'${filteredProducts[i].stocksSumProductStock ?? 0}',
style: _theme.textTheme.bodyMedium?.copyWith(
fontSize: 14,
fontWeight: FontWeight.w400,
color: DAppColors.kSecondary,
),
),
],
),
onTap: () {
final Stock? stock = ((filteredProducts[i].stocks?.isEmpty ?? true) || filteredProducts[i].stocks == null) ? null : filteredProducts[i].stocks?.first;
final cartProduct = CartProductModelPurchase(
productId: filteredProducts[i].id ?? 0,
brandName: filteredProducts[i].brand?.brandName ?? '',
productName: filteredProducts[i].productName ?? '',
productDealerPrice: stock?.productDealerPrice ?? 0,
productPurchasePrice: stock?.productPurchasePrice ?? 0,
productSalePrice: stock?.productSalePrice ?? 0,
productWholeSalePrice: stock?.productWholeSalePrice ?? 0,
quantities: 1,
productType: filteredProducts[i].productType ?? ProductType.single.name,
vatAmount: filteredProducts[i].vatAmount ?? 0,
vatRate: filteredProducts[i].vat?.rate ?? 0,
vatType: filteredProducts[i].vatType ?? 'exclusive',
expireDate: stock?.expireDate,
mfgDate: stock?.mfgDate,
profitPercent: stock?.profitPercent ?? 0,
stock: filteredProducts[i].stocksSumProductStock,
);
addProductInPurchaseCartButtomSheet(
context: context, product: cartProduct, ref: ref, fromUpdate: false, index: 0, fromStock: false, stocks: filteredProducts[i].stocks ?? []);
},
),
);
});
}, error: (e, stack) {
return Text(e.toString());
}, loading: () {
return const Center(child: CircularProgressIndicator());
}),
],
),
),
),
);
});
}
}
// ignore: must_be_immutable
class ProductCard extends StatefulWidget {
ProductCard({super.key, required this.productTitle, required this.productDescription, required this.stock, required this.productImage});
// final Product product;
String productTitle, productDescription, stock;
String? productImage;
@override
State<ProductCard> createState() => _ProductCardState();
}
class _ProductCardState extends State<ProductCard> {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Consumer(builder: (context, ref, __) {
return Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
children: [
Padding(
padding: const EdgeInsets.all(4.0),
child: Container(
height: 50,
width: 50,
decoration: widget.productImage == null
? BoxDecoration(
image: DecorationImage(image: AssetImage(noProductImageUrl), fit: BoxFit.cover),
borderRadius: BorderRadius.circular(90.0),
)
: BoxDecoration(
image: DecorationImage(image: NetworkImage("${APIConfig.domain}${widget.productImage}"), fit: BoxFit.cover),
borderRadius: BorderRadius.circular(90.0),
),
),
),
Padding(
padding: const EdgeInsets.only(left: 10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Text(
widget.productTitle,
style: theme.textTheme.titleLarge,
),
],
),
Text(
widget.productDescription,
style: theme.textTheme.bodyLarge,
),
],
),
),
const Spacer(),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
lang.S.of(context).stock,
style: theme.textTheme.titleLarge?.copyWith(
fontSize: 18,
),
),
Text(
widget.stock,
style: theme.textTheme.bodyLarge?.copyWith(
color: kGreyTextColor,
),
),
],
),
],
),
);
});
}
}