first commit
This commit is contained in:
502
lib/Screens/Purchase/Model/purchase_transaction_model.dart
Normal file
502
lib/Screens/Purchase/Model/purchase_transaction_model.dart
Normal 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;
|
||||
}
|
||||
330
lib/Screens/Purchase/Repo/purchase_repo.dart
Normal file
330
lib/Screens/Purchase/Repo/purchase_repo.dart
Normal 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,
|
||||
};
|
||||
}
|
||||
1196
lib/Screens/Purchase/add_and_edit_purchase.dart
Normal file
1196
lib/Screens/Purchase/add_and_edit_purchase.dart
Normal file
File diff suppressed because it is too large
Load Diff
332
lib/Screens/Purchase/bulk purchase/bulk_purchase.dart
Normal file
332
lib/Screens/Purchase/bulk purchase/bulk_purchase.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
203
lib/Screens/Purchase/choose_supplier_screen.dart
Normal file
203
lib/Screens/Purchase/choose_supplier_screen.dart
Normal 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(),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
222
lib/Screens/Purchase/purchase_list.dart
Normal file
222
lib/Screens/Purchase/purchase_list.dart
Normal 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 [],
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
617
lib/Screens/Purchase/purchase_product_buttom_sheet.dart
Normal file
617
lib/Screens/Purchase/purchase_product_buttom_sheet.dart
Normal 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 '';
|
||||
}
|
||||
}
|
||||
314
lib/Screens/Purchase/purchase_products.dart
Normal file
314
lib/Screens/Purchase/purchase_products.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user