Files
kulakpos_app/lib/Screens/Purchase/add_and_edit_purchase.dart

1197 lines
64 KiB
Dart
Raw Normal View History

2026-02-07 15:57:09 +07:00
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hugeicons/hugeicons.dart';
import 'package:iconly/iconly.dart';
import 'package:mobile_pos/Provider/profile_provider.dart';
import 'package:mobile_pos/Screens/Purchase/Model/purchase_transaction_model.dart';
import 'package:mobile_pos/Screens/Purchase/bulk%20purchase/bulk_purchase.dart';
import 'package:mobile_pos/Screens/Purchase/purchase_product_buttom_sheet.dart';
import 'package:mobile_pos/Screens/Purchase/purchase_products.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import 'package:nb_utils/nb_utils.dart';
import '../../GlobalComponents/glonal_popup.dart';
import '../../Provider/add_to_cart_purchase.dart';
import '../../Repository/API/future_invoice.dart';
import '../../constant.dart';
import '../../currency.dart';
import '../../widgets/multipal payment mathods/multi_payment_widget.dart';
import '../Customers/Model/parties_model.dart' as party;
import '../Home/home.dart';
import '../Products/add product/modle/create_product_model.dart';
import '../Purchase List/purchase_list_screen.dart';
import '../../service/check_user_role_permission_provider.dart';
import '../invoice_details/purchase_invoice_details.dart';
import '../vat_&_tax/model/vat_model.dart';
import '../vat_&_tax/provider/text_repo.dart';
import 'Repo/purchase_repo.dart';
class AddAndUpdatePurchaseScreen extends ConsumerStatefulWidget {
AddAndUpdatePurchaseScreen({super.key, required this.customerModel, this.transitionModel});
party.Party? customerModel;
final PurchaseTransaction? transitionModel;
@override
AddSalesScreenState createState() => AddSalesScreenState();
}
class AddSalesScreenState extends ConsumerState<AddAndUpdatePurchaseScreen> {
// Key to access MultiPaymentWidget State
final GlobalKey<MultiPaymentWidgetState> paymentWidgetKey = GlobalKey();
bool isProcessing = false;
DateTime selectedDate = DateTime.now();
TextEditingController dateController = TextEditingController(text: DateTime.now().toString().substring(0, 10));
TextEditingController phoneController = TextEditingController();
TextEditingController recevedAmountController = TextEditingController();
// To handle initial logic
bool _initialingFirstTime = false;
@override
void initState() {
super.initState();
// Listener to calculate prices when payment widget updates the total amount
recevedAmountController.addListener(() {
final cart = ref.read(cartNotifierPurchaseNew);
cart.calculatePrice(receivedAmount: recevedAmountController.text, stopRebuild: !_initialingFirstTime);
});
if (widget.transitionModel != null) {
final editedSales = widget.transitionModel;
dateController.text = editedSales?.purchaseDate?.substring(0, 10) ?? '';
recevedAmountController.text = editedSales?.paidAmount.toString() ?? '';
widget.customerModel = party.Party(
id: widget.transitionModel?.party?.id,
name: widget.transitionModel?.party?.name,
);
if (widget.transitionModel?.discountType == 'flat') {
discountType = 'Flat';
} else {
discountType = 'Percent';
}
addProductsInCartFromEditList();
}
_initialingFirstTime = true;
}
String flatValue = 'Flat';
String percentValue = 'Percent';
@override
void dispose() {
dateController.dispose();
phoneController.dispose();
recevedAmountController.dispose();
super.dispose();
}
void addProductsInCartFromEditList() {
final cart = ref.read(cartNotifierPurchaseNew);
if (widget.transitionModel?.details?.isNotEmpty ?? false) {
for (var detail in widget.transitionModel!.details!) {
cart.addToCartRiverPod(
cartItem: CartProductModelPurchase(
warehouseId: detail.stock?.warehouseId,
productName: detail.product?.productName ?? '',
productId: detail.productId ?? 0,
quantities: detail.quantities,
vatType: detail.product?.vatType ?? 'exclusive',
vatRate: detail.product?.vat?.rate ?? 0,
vatAmount: detail.product?.vatAmount ?? 0,
productType: detail.product?.productType ?? ProductType.single.name,
profitPercent: detail.profitPercent,
mfgDate: detail.mfgDate,
expireDate: detail.expireDate,
batchNumber: detail.stock?.batchNo ?? '',
productWholeSalePrice: detail.productWholeSalePrice ?? 0,
productSalePrice: detail.productSalePrice ?? 0,
productPurchasePrice: detail.productPurchasePrice,
productDealerPrice: detail.productDealerPrice ?? 0,
stock: detail.productStock ?? 0,
),
fromEditSales: true,
isVariation: detail.product?.productType == ProductType.variant.name);
}
}
cart.discountAmount = widget.transitionModel?.discountAmount ?? 0;
if (widget.transitionModel?.discountType == 'flat') {
cart.discountTextControllerFlat.text = widget.transitionModel?.discountAmount.toString() ?? '';
} else {
cart.discountTextControllerFlat.text = widget.transitionModel?.discountPercent?.toString() ?? '';
}
cart.finalShippingCharge = widget.transitionModel?.shippingCharge ?? 0;
cart.shippingChargeController.text = widget.transitionModel?.shippingCharge.toString() ?? '';
cart.vatAmountController.text = widget.transitionModel?.vatAmount.toString() ?? '';
cart.calculatePrice(receivedAmount: widget.transitionModel?.paidAmount.toString(), stopRebuild: true);
}
bool hasPreselected = false;
String discountType = 'Flat';
@override
Widget build(BuildContext context) {
final _theme = Theme.of(context);
final providerData = ref.watch(cartNotifierPurchaseNew);
final personalData = ref.watch(businessInfoProvider);
final taxesData = ref.watch(taxProvider);
final permissionService = PermissionService(ref);
return personalData.when(data: (data) {
final _theme = Theme.of(context);
return GlobalPopup(
child: Scaffold(
backgroundColor: kWhite,
appBar: AppBar(
backgroundColor: Colors.white,
title: Text(
widget.transitionModel == null ? lang.S.of(context).addPurchase : 'Update Purchase',
),
centerTitle: true,
iconTheme: const IconThemeData(color: Colors.black),
elevation: 2.0,
surfaceTintColor: kWhite,
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
///_______Invoice_And_Date_____________________________________________________
Row(
children: [
widget.transitionModel == null
? FutureBuilder(
future: FutureInvoice().getFutureInvoice(tag: 'purchases'),
builder: (context, snapshot) {
if (snapshot.hasData) {
final invoiceValue =
(snapshot.data != null) ? snapshot.data.toString().replaceAll('"', '') : '';
return Expanded(
child: AppTextField(
textFieldType: TextFieldType.NAME,
initialValue: invoiceValue ?? '',
readOnly: true,
decoration: InputDecoration(
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: lang.S.of(context).inv,
border: const OutlineInputBorder(),
),
),
);
} else {
return Expanded(
child: TextFormField(
readOnly: true,
decoration: InputDecoration(
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: lang.S.of(context).inv,
border: const OutlineInputBorder(),
),
),
);
}
},
)
: Expanded(
child: AppTextField(
textFieldType: TextFieldType.NAME,
initialValue: widget.transitionModel?.invoiceNumber,
readOnly: true,
decoration: InputDecoration(
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: lang.S.of(context).inv,
border: const OutlineInputBorder(),
),
),
),
const SizedBox(width: 20),
Expanded(
child: TextFormField(
readOnly: true,
controller: dateController,
decoration: InputDecoration(
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: lang.S.of(context).date,
suffixIconConstraints: const BoxConstraints(
minWidth: 20,
minHeight: 20,
),
suffixIcon: IconButton(
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
onPressed: () async {
final DateTime? picked = await showDatePicker(
initialDate: selectedDate,
firstDate: DateTime(2015, 8),
lastDate: DateTime(2101),
context: context,
);
if (picked != null && picked != selectedDate) {
setState(() {
selectedDate = selectedDate.copyWith(
year: picked.year,
month: picked.month,
day: picked.day,
);
dateController.text = selectedDate.toString().substring(0, 10);
});
}
},
icon: Icon(IconlyLight.calendar),
),
),
),
),
],
),
///______Selected_Due_And_Customer___________________________________________
const SizedBox(height: 20),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(lang.S.of(context).dueAmount),
Text(
widget.customerModel?.due == null ? '$currency 0' : '$currency${widget.customerModel?.due}',
style: const TextStyle(color: Color(0xFFFF8C34)),
),
],
),
const SizedBox(
height: 10,
),
AppTextField(
textFieldType: TextFieldType.NAME,
readOnly: true,
initialValue: widget.customerModel?.name ?? 'Guest',
decoration: InputDecoration(
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: lang.S.of(context).customerName,
border: const OutlineInputBorder(),
),
),
Visibility(
visible: widget.customerModel == null,
child: Padding(
padding: const EdgeInsets.only(top: 20.0),
child: AppTextField(
controller: phoneController,
textFieldType: TextFieldType.PHONE,
decoration: kInputDecoration.copyWith(
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: lang.S.of(context).customerPhoneNumber,
hintText: lang.S.of(context).enterCustomerPhoneNumber,
),
),
),
),
],
),
const SizedBox(height: 20),
///_______Added_Items_List_________________________________________________
providerData.cartItemList.isNotEmpty
? Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Theme(
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
child: ExpansionTile(
initiallyExpanded: true,
collapsedBackgroundColor: kMainColor2,
backgroundColor: kMainColor2,
//
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(
color: kLineColor,
width: 1,
),
),
title: Text(
lang.S.of(context).itemAdded,
style: _theme.textTheme.titleMedium,
),
children: [
Container(
color: Colors.white,
child: ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: providerData.cartItemList.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(left: 10, right: 10),
child: ListTile(
onTap: () {
addProductInPurchaseCartButtomSheet(
product: providerData.cartItemList[index],
ref: ref,
fromUpdate: true,
context: context,
index: index,
fromStock: false,
stocks: []);
},
contentPadding: const EdgeInsets.all(0),
title: Text(providerData.cartItemList[index].productName.toString()),
subtitle: permissionService.hasPermission(Permit.purchasesPriceView.value)
? Text(
'${providerData.cartItemList[index].quantities} X ${providerData.cartItemList[index].productPurchasePrice} = ${formatPointNumber((providerData.cartItemList[index].quantities ?? 0) * (providerData.cartItemList[index].productPurchasePrice ?? 0))} ${providerData.cartItemList[index].productType == ProductType.variant.name ? "[${providerData.cartItemList[index].batchNumber.isEmptyOrNull ? 'N/A' : providerData.cartItemList[index].batchNumber}]" : ''}')
: null,
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 80,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () {
providerData.quantityDecrease(index);
},
child: Container(
height: 18,
width: 18,
decoration: const BoxDecoration(
color: kMainColor,
borderRadius: BorderRadius.all(Radius.circular(10)),
),
child: const Center(
child: Icon(Icons.remove, size: 14, color: Colors.white),
),
),
),
const SizedBox(width: 5),
SizedBox(
width: 30,
child: Center(
child: Text(
providerData.cartItemList[index].quantities.toString(),
),
),
),
const SizedBox(width: 5),
GestureDetector(
onTap: () {
providerData.quantityIncrease(index);
},
child: Container(
height: 18,
width: 18,
decoration: const BoxDecoration(
color: kMainColor,
borderRadius: BorderRadius.all(Radius.circular(10)),
),
child: const Center(
child: Icon(Icons.add, size: 14, color: Colors.white),
),
),
),
],
),
),
const SizedBox(width: 10),
GestureDetector(
onTap: () {
providerData.deleteToCart(index);
},
child: HugeIcon(
icon: HugeIcons.strokeRoundedDelete03,
size: 19,
color: Colors.red,
),
),
],
),
),
);
}),
)
],
),
),
)
: SizedBox.shrink(),
// Padding(
// padding: const EdgeInsets.only(bottom: 20.0),
// child: Container(
// decoration: BoxDecoration(
// borderRadius: const BorderRadius.only(topLeft: Radius.circular(10), topRight: Radius.circular(10)),
// border: Border.all(width: 1, color: const Color(0xffEAEFFA)),
// ),
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Container(
// width: double.infinity,
// decoration: const BoxDecoration(
// color: Color(0xffEAEFFA),
// borderRadius: BorderRadius.only(topLeft: Radius.circular(10), topRight: Radius.circular(10)),
// ),
// child: Padding(
// padding: const EdgeInsets.all(10),
// child: SizedBox(
// width: context.width() / 1.35,
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text(
// lang.S.of(context).itemAdded,
// style: const TextStyle(fontSize: 16),
// ),
// Text(
// lang.S.of(context).quantity,
// style: const TextStyle(fontSize: 16),
// ),
// ],
// ),
// ),
// )),
// ListView.builder(
// shrinkWrap: true,
// physics: const NeverScrollableScrollPhysics(),
// itemCount: providerData.cartItemList.length,
// itemBuilder: (context, index) {
// return Padding(
// padding: const EdgeInsets.only(left: 10, right: 10),
// child: ListTile(
// onTap: () {
// addProductInPurchaseCartButtomSheet(
// product: providerData.cartItemList[index], ref: ref, fromUpdate: true, context: context, index: index, fromStock: false, stocks: []);
// },
// contentPadding: const EdgeInsets.all(0),
// title: Text(providerData.cartItemList[index].productName.toString()),
// subtitle: permissionService.hasPermission(Permit.purchasesPriceView.value)
// ? Text(
// '${providerData.cartItemList[index].quantities} X ${providerData.cartItemList[index].productPurchasePrice} = ${formatPointNumber((providerData.cartItemList[index].quantities ?? 0) * (providerData.cartItemList[index].productPurchasePrice ?? 0))} ${providerData.cartItemList[index].productType == ProductType.variant.name ? "[${providerData.cartItemList[index].batchNumber.isEmptyOrNull ? 'N/A' : providerData.cartItemList[index].batchNumber}]" : ''}')
// : null,
// trailing: Row(
// mainAxisSize: MainAxisSize.min,
// children: [
// SizedBox(
// width: 80,
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// GestureDetector(
// onTap: () {
// providerData.quantityDecrease(index);
// },
// child: Container(
// height: 20,
// width: 20,
// decoration: const BoxDecoration(
// color: kMainColor,
// borderRadius: BorderRadius.all(Radius.circular(10)),
// ),
// child: const Center(
// child: Icon(Icons.remove, size: 14, color: Colors.white),
// ),
// ),
// ),
// const SizedBox(width: 5),
// SizedBox(
// width: 30,
// child: Center(
// child: Text(
// providerData.cartItemList[index].quantities.toString(),
// ),
// ),
// ),
// const SizedBox(width: 5),
// GestureDetector(
// onTap: () {
// providerData.quantityIncrease(index);
// },
// child: Container(
// height: 20,
// width: 20,
// decoration: const BoxDecoration(
// color: kMainColor,
// borderRadius: BorderRadius.all(Radius.circular(10)),
// ),
// child: const Center(
// child: Icon(Icons.add, size: 14, color: Colors.white),
// ),
// ),
// ),
// ],
// ),
// ),
// const SizedBox(width: 10),
// GestureDetector(
// onTap: () {
// providerData.deleteToCart(index);
// },
// child: Container(
// padding: const EdgeInsets.all(4),
// color: Colors.red.withOpacity(0.1),
// child: const Icon(
// Icons.delete,
// size: 20,
// color: Colors.red,
// ),
// ),
// ),
// ],
// ),
// ),
// );
// }),
// ],
// ),
// )).visible(providerData.cartItemList.isNotEmpty),
///_______Add_Button__________________________________________________
Row(
spacing: 10,
children: [
Expanded(
child: ElevatedButton(
onPressed: () {
PurchaseProducts(
customerModel: widget.customerModel,
).launch(context);
},
style: ElevatedButton.styleFrom(
elevation: 0.0,
backgroundColor: kMainColor2,
minimumSize: Size.fromHeight(40),
),
child: Text(lang.S.of(context).addItems,
style: _theme.textTheme.titleMedium?.copyWith(
color: kMainColor,
)),
),
),
GestureDetector(
onTap: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => const BulkPurchaseUploader()));
},
child: Container(
height: 48,
width: 60,
decoration: BoxDecoration(
color: Colors.green.withOpacity(0.1),
borderRadius: const BorderRadius.all(Radius.circular(10))),
child: Center(
child: Image(
height: 40,
width: 40,
image: AssetImage('images/file-upload.png'),
),
),
),
),
],
),
const SizedBox(height: 20),
///_____Total_Section_____________________________
Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(10)),
border: Border.all(color: Colors.grey.shade300, width: 1)),
child: Column(
children: [
///________Total_title_reader_________________________
Container(
padding: const EdgeInsets.all(10),
decoration: const BoxDecoration(
color: Color(0xffFEF0F1),
borderRadius:
BorderRadius.only(topRight: Radius.circular(10), topLeft: Radius.circular(10))),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
lang.S.of(context).subTotal,
style: _theme.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w500,
),
),
Text(
formatPointNumber(providerData.totalAmount),
style: _theme.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w500,
),
),
],
),
),
///_________Discount___________________________________
Padding(
padding: const EdgeInsets.only(right: 10, left: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
lang.S.of(context).discount,
style: _theme.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w500,
color: kPeraColor,
),
),
const Spacer(),
SizedBox(
width: context.width() / 4,
height: 30,
child: Container(
decoration: const BoxDecoration(
border: Border(bottom: BorderSide(color: kBorder, width: 1)),
),
child: DropdownButton<String?>(
dropdownColor: Colors.white,
isExpanded: true,
isDense: true,
padding: EdgeInsets.zero,
icon: const Icon(
Icons.keyboard_arrow_down,
color: kPeraColor,
size: 18,
),
hint: Text(
lang.S.of(context).select,
style: _theme.textTheme.bodyMedium?.copyWith(
color: kGreyTextColor,
),
),
value: discountType,
items: [
DropdownMenuItem<String>(
value: flatValue,
child: Text(lang.S.of(context).flat),
),
DropdownMenuItem<String>(
value: percentValue,
child: Text(lang.S.of(context).percent),
),
],
onChanged: (value) {
setState(() {
discountType = value!;
providerData.calculateDiscount(
value: providerData.discountTextControllerFlat.text,
selectedTaxType: discountType,
);
});
},
),
),
),
const SizedBox(width: 10),
SizedBox(
width: context.width() / 4,
height: 30,
child: TextField(
controller: providerData.discountTextControllerFlat,
onChanged: (value) {
setState(() {
providerData.calculateDiscount(
value: value,
selectedTaxType: discountType,
);
});
},
textAlign: TextAlign.right,
style: _theme.textTheme.titleSmall,
decoration: InputDecoration(
hintText: '0',
hintStyle: _theme.textTheme.titleMedium?.copyWith(
color: kPeraColor,
),
border: UnderlineInputBorder(borderSide: BorderSide(color: kBorder)),
enabledBorder: UnderlineInputBorder(borderSide: BorderSide(color: kBorder)),
focusedBorder: UnderlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 0, vertical: 8),
),
keyboardType: TextInputType.number,
),
),
],
),
),
///_________Vat_Dropdown_______________________________
Padding(
padding: const EdgeInsets.only(right: 10, left: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text(
lang.S.of(context).vat,
style: _theme.textTheme.titleSmall?.copyWith(color: kPeraColor),
),
const Spacer(),
taxesData.when(
data: (data) {
List<VatModel> dataList = data.where((tax) => tax.status == true).toList();
if (widget.transitionModel != null &&
widget.transitionModel?.vatId != null &&
!hasPreselected) {
VatModel matched = dataList.firstWhere(
(element) => element.id == widget.transitionModel?.vatId,
orElse: () => VatModel(),
);
if (matched.id != null) {
hasPreselected = true;
providerData.selectedVat = matched;
}
}
return SizedBox(
width: context.width() / 4,
height: 30,
child: Container(
decoration: const BoxDecoration(
border: Border(bottom: BorderSide(color: kBorder, width: 1)),
),
child: DropdownButton<VatModel?>(
icon: providerData.selectedVat != null
? GestureDetector(
onTap: () => providerData.changeSelectedVat(data: null),
child: const Icon(
Icons.close,
color: Colors.red,
size: 16,
),
)
: const Icon(
Icons.keyboard_arrow_down,
color: kPeraColor,
size: 18,
),
dropdownColor: Colors.white,
isExpanded: true,
isDense: true,
padding: EdgeInsets.zero,
hint: Text(
lang.S.of(context).selectOne,
style: _theme.textTheme.bodyMedium?.copyWith(
color: kPeraColor,
),
),
value: providerData.selectedVat,
items: dataList.map((VatModel tax) {
return DropdownMenuItem<VatModel>(
value: tax,
child: Text(
tax.name ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: _theme.textTheme.bodyMedium?.copyWith(
color: kPeraColor,
),
),
);
}).toList(),
onChanged: (VatModel? newValue) {
providerData.changeSelectedVat(data: newValue);
},
),
),
);
},
error: (error, stackTrace) {
return Text(error.toString());
},
loading: () {
return const SizedBox.shrink();
},
),
const SizedBox(width: 10),
SizedBox(
width: context.width() / 4,
height: 30,
child: TextFormField(
controller: providerData.vatAmountController,
style: _theme.textTheme.titleSmall,
readOnly: true,
onChanged: (value) => providerData.calculateDiscount(
value: value, selectedTaxType: discountType.toString()),
textAlign: TextAlign.right,
decoration: InputDecoration(
hintText: '0',
hintStyle: _theme.textTheme.titleSmall?.copyWith(
color: kPeraColor,
),
border: UnderlineInputBorder(borderSide: BorderSide(color: kBorder)),
enabledBorder: UnderlineInputBorder(borderSide: BorderSide(color: kBorder)),
focusedBorder: UnderlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 0, vertical: 8),
),
keyboardType: TextInputType.number,
),
),
],
),
),
Padding(
padding: const EdgeInsets.only(right: 10, left: 10, top: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
lang.S.of(context).shippingCharge,
style: _theme.textTheme.titleSmall?.copyWith(
color: kPeraColor,
),
),
SizedBox(
width: context.width() / 4,
height: 30,
child: TextFormField(
controller: providerData.shippingChargeController,
style: _theme.textTheme.titleSmall,
keyboardType: TextInputType.number,
onChanged: (value) =>
providerData.calculatePrice(shippingCharge: value.isEmpty ? '0' : value),
textAlign: TextAlign.right,
decoration: InputDecoration(
hintText: '0',
hintStyle: _theme.textTheme.titleSmall?.copyWith(
color: kPeraColor,
),
border: UnderlineInputBorder(borderSide: BorderSide(color: kBorder)),
enabledBorder: UnderlineInputBorder(borderSide: BorderSide(color: kBorder)),
focusedBorder: UnderlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 0, vertical: 8),
),
),
),
],
),
),
///________Total_______________________________________
Padding(
padding: const EdgeInsets.only(right: 10, left: 10, top: 7),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
lang.S.of(context).total,
style: _theme.textTheme.titleSmall?.copyWith(
color: kPeraColor,
),
),
Text(
formatPointNumber(providerData.totalPayableAmount),
style: _theme.textTheme.titleSmall?.copyWith(
color: kPeraColor,
),
),
],
),
),
///________paid_Amount__________________________________
Padding(
padding: const EdgeInsets.only(right: 10, left: 10, top: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
lang.S.of(context).paidAmount,
style: _theme.textTheme.titleSmall?.copyWith(
color: kPeraColor,
),
),
SizedBox(
width: context.width() / 4,
height: 30,
child: TextField(
controller: recevedAmountController,
// Make it readOnly if using multiple payments to force usage of the widget
readOnly: (paymentWidgetKey.currentState?.getPaymentEntries().length ?? 1) > 1,
keyboardType: TextInputType.number,
textAlign: TextAlign.right,
style: _theme.textTheme.titleSmall,
decoration: InputDecoration(
hintText: '0',
hintStyle: _theme.textTheme.titleSmall?.copyWith(
color: kPeraColor,
),
border: UnderlineInputBorder(borderSide: BorderSide(color: kBorder)),
enabledBorder: UnderlineInputBorder(borderSide: BorderSide(color: kBorder)),
focusedBorder: UnderlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 0, vertical: 8),
),
),
),
],
),
),
///________Change Amount_________________________________
Visibility(
visible: providerData.changeAmount > 0,
child: Padding(
padding: const EdgeInsets.only(right: 10, left: 10, top: 13, bottom: 13),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
lang.S.of(context).changeAmount,
style: _theme.textTheme.titleSmall?.copyWith(
color: kPeraColor,
),
),
Text(
formatPointNumber(providerData.changeAmount),
style: _theme.textTheme.titleSmall?.copyWith(
color: kPeraColor,
),
),
],
),
),
),
///_______Due_amount_____________________________________
Visibility(
visible: providerData.dueAmount > 0 ||
(providerData.changeAmount == 0 && providerData.dueAmount == 0),
child: Padding(
padding: const EdgeInsets.only(right: 10, left: 10, top: 13, bottom: 13),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
lang.S.of(context).dueAmount,
style: _theme.textTheme.titleSmall?.copyWith(
color: kPeraColor,
),
),
Text(
formatPointNumber(providerData.dueAmount),
style: _theme.textTheme.titleSmall?.copyWith(
color: kPeraColor,
),
),
],
),
),
),
],
),
),
const SizedBox(height: 20),
///_______Payment_Type_______________________________
MultiPaymentWidget(
key: paymentWidgetKey,
showWalletOption: true,
totalAmountController: recevedAmountController,
showChequeOption: false,
initialTransactions: widget.transitionModel?.transactions,
onPaymentListChanged: () {
providerData.calculatePrice(receivedAmount: recevedAmountController.text);
},
),
const SizedBox(height: 24),
///_____Action_Button_____________________________________
Row(
children: [
Expanded(
child: OutlinedButton(
style: OutlinedButton.styleFrom(
maximumSize: const Size(double.infinity, 48),
minimumSize: const Size(double.infinity, 48),
disabledBackgroundColor: _theme.colorScheme.primary.withValues(alpha: 0.15),
),
onPressed: () async {
const Home().launch(context, isNewTask: true);
},
child: Text(
lang.S.of(context).cancel,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: _theme.textTheme.bodyMedium?.copyWith(
color: _theme.colorScheme.primary,
fontWeight: FontWeight.w600,
fontSize: 16,
),
),
),
),
const SizedBox(width: 20),
Expanded(
child: ElevatedButton(
style: OutlinedButton.styleFrom(
maximumSize: const Size(double.infinity, 48),
minimumSize: const Size(double.infinity, 48),
disabledBackgroundColor: _theme.colorScheme.primary.withValues(alpha: 0.15),
),
onPressed: () async {
if (providerData.cartItemList.isEmpty) {
EasyLoading.showError(lang.S.of(context).addProductFirst);
return;
}
if (widget.customerModel == null && providerData.dueAmount > 0) {
EasyLoading.showError(lang.S.of(context).dueSaleWarn);
return;
}
// Validate Payments from the Widget
List<PaymentEntry> payments = paymentWidgetKey.currentState?.getPaymentEntries() ?? [];
if (payments.isEmpty) {
EasyLoading.showError('Please select at least one payment method');
return;
}
// Prevent multiple clicks
if (isProcessing) return;
setState(() {
isProcessing = true;
});
try {
EasyLoading.show(status: lang.S.of(context).loading, dismissOnTap: false);
// Serialize Payment List for API
List<Map<String, dynamic>> paymentData = payments.map((e) => e.toJson()).toList();
PurchaseRepo repo = PurchaseRepo();
PurchaseTransaction? purchaseData;
if (widget.transitionModel == null) {
/// CREATE
if (!permissionService.hasPermission(Permit.purchasesCreate.value)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Colors.red,
content: Text(lang.S.of(context).purchaseWarn),
),
);
return;
}
purchaseData = await repo.createPurchase(
ref: ref,
context: context,
vatId: providerData.selectedVat?.id,
totalAmount: providerData.totalPayableAmount,
purchaseDate: selectedDate.toIso8601String(),
products: providerData.cartItemList,
vatAmount: providerData.vatAmount,
vatPercent: providerData.selectedVat?.rate ?? 0,
paymentType: paymentData, // Passing JSON String
partyId: widget.customerModel?.id ?? 0,
isPaid: providerData.dueAmount <= 0,
dueAmount: providerData.dueAmount <= 0 ? 0 : providerData.dueAmount,
discountAmount: providerData.discountAmount,
changeAmount: providerData.changeAmount,
shippingCharge: providerData.finalShippingCharge,
discountPercent: providerData.discountPercent,
discountType: discountType.toLowerCase() ?? '',
);
if (purchaseData != null) {
final refreshed = await repo.getSinglePurchase((purchaseData.id ?? 0).toInt());
if (refreshed == null) {
PurchaseInvoiceDetails(
businessInfo: personalData.value!,
transitionModel: purchaseData,
isFromPurchase: true,
).launch(context);
return;
}
PurchaseInvoiceDetails(
businessInfo: personalData.value!,
transitionModel: refreshed,
isFromPurchase: true,
).launch(context);
}
} else {
/// UPDATE
if (!permissionService.hasPermission(Permit.purchasesUpdate.value)) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
backgroundColor: Colors.red,
content: Text(lang.S.of(context).purchaseUpdateWarn)));
return;
}
print('-----payment type update-------$paymentData------------');
purchaseData = await repo.updatePurchase(
id: widget.transitionModel!.id!,
ref: ref,
context: context,
vatId: providerData.selectedVat?.id,
totalAmount: providerData.totalPayableAmount,
purchaseDate: selectedDate.toString(),
products: providerData.cartItemList,
vatAmount: providerData.vatAmount,
vatPercent: providerData.selectedVat?.rate ?? 0,
paymentType: paymentData, // Passing JSON String
changeAmount: providerData.changeAmount,
partyId: widget.transitionModel?.party?.id ?? 0,
isPaid: providerData.dueAmount <= 0,
dueAmount: providerData.dueAmount <= 0 ? 0 : providerData.dueAmount,
discountAmount: providerData.discountAmount,
);
if (purchaseData != null) {
const PurchaseListScreen().launch(context);
}
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString())));
} finally {
EasyLoading.dismiss();
setState(() {
isProcessing = false;
});
}
},
child: Text(
lang.S.of(context).save,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: _theme.textTheme.bodyMedium?.copyWith(
color: _theme.colorScheme.primaryContainer,
fontWeight: FontWeight.w600,
fontSize: 16,
),
),
),
),
],
),
],
),
),
),
),
);
}, error: (e, stack) {
return Center(
child: Text(e.toString()),
);
}, loading: () {
return const Center(child: CircularProgressIndicator());
});
}
}