first commit
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
class DueCollectionInvoice {
|
||||
DueCollectionInvoice({
|
||||
this.id,
|
||||
this.due,
|
||||
this.name,
|
||||
this.type,
|
||||
this.salesDues,
|
||||
});
|
||||
|
||||
DueCollectionInvoice.fromJson(dynamic json) {
|
||||
id = json['id'];
|
||||
due = json['due'];
|
||||
name = json['name'];
|
||||
type = json['type'];
|
||||
if (json[json['type'] == 'Supplier' ? 'purchases_dues' : 'sales_dues'] != null) {
|
||||
salesDues = [];
|
||||
json[json['type'] == 'Supplier' ? 'purchases_dues' : 'sales_dues'].forEach((v) {
|
||||
salesDues?.add(SalesDuesInvoice.fromJson(v));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
num? id;
|
||||
num? due;
|
||||
String? name;
|
||||
String? type;
|
||||
List<SalesDuesInvoice>? salesDues;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
map['id'] = id;
|
||||
map['due'] = due;
|
||||
map['name'] = name;
|
||||
map['type'] = type;
|
||||
if (salesDues != null) {
|
||||
map['sales_dues'] = salesDues?.map((v) => v.toJson()).toList();
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
class SalesDuesInvoice {
|
||||
SalesDuesInvoice({
|
||||
this.id,
|
||||
this.partyId,
|
||||
this.dueAmount,
|
||||
this.paidAmount,
|
||||
this.totalAmount,
|
||||
this.invoiceNumber,
|
||||
});
|
||||
|
||||
SalesDuesInvoice.fromJson(dynamic json) {
|
||||
id = json['id'];
|
||||
partyId = json['party_id'];
|
||||
dueAmount = json['dueAmount'];
|
||||
paidAmount = json['paidAmount'];
|
||||
totalAmount = json['totalAmount'];
|
||||
invoiceNumber = json['invoiceNumber'];
|
||||
}
|
||||
|
||||
num? id;
|
||||
num? partyId;
|
||||
num? dueAmount;
|
||||
num? paidAmount;
|
||||
num? totalAmount;
|
||||
String? invoiceNumber;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
map['id'] = id;
|
||||
map['party_id'] = partyId;
|
||||
map['dueAmount'] = dueAmount;
|
||||
map['paidAmount'] = paidAmount;
|
||||
map['totalAmount'] = totalAmount;
|
||||
map['invoiceNumber'] = invoiceNumber;
|
||||
return map;
|
||||
}
|
||||
}
|
||||
144
lib/Screens/Due Calculation/Model/due_collection_model.dart
Normal file
144
lib/Screens/Due Calculation/Model/due_collection_model.dart
Normal file
@@ -0,0 +1,144 @@
|
||||
import '../../../model/sale_transaction_model.dart';
|
||||
import '../../../widgets/multipal payment mathods/model/payment_transaction_model.dart';
|
||||
import '../../Customers/Model/parties_model.dart';
|
||||
|
||||
class DueCollection {
|
||||
DueCollection(
|
||||
{this.id,
|
||||
this.businessId,
|
||||
this.partyId,
|
||||
this.userId,
|
||||
this.saleId,
|
||||
this.purchaseId,
|
||||
this.totalDue,
|
||||
this.dueAmountAfterPay,
|
||||
this.payDueAmount,
|
||||
this.paymentTypeId,
|
||||
this.paymentType,
|
||||
this.paymentDate,
|
||||
this.invoiceNumber,
|
||||
this.createdAt,
|
||||
this.updatedAt,
|
||||
this.user,
|
||||
this.party,
|
||||
this.transactions,
|
||||
this.branch});
|
||||
|
||||
DueCollection.fromJson(dynamic json) {
|
||||
id = json['id'];
|
||||
businessId = json['business_id'];
|
||||
partyId = json['party_id'];
|
||||
userId = json['user_id'];
|
||||
saleId = json['sale_id'];
|
||||
purchaseId = json['purchase_id'];
|
||||
totalDue = json['totalDue'];
|
||||
dueAmountAfterPay = json['dueAmountAfterPay'];
|
||||
payDueAmount = json['payDueAmount'];
|
||||
paymentTypeId = int.tryParse(json["payment_type_id"].toString());
|
||||
// paymentType = json['paymentType'];
|
||||
paymentDate = json['paymentDate'];
|
||||
invoiceNumber = json['invoiceNumber'];
|
||||
createdAt = json['created_at'];
|
||||
updatedAt = json['updated_at'];
|
||||
user = json['user'] != null ? User.fromJson(json['user']) : null;
|
||||
party = json['party'] != null ? Party.fromJson(json['party']) : null;
|
||||
paymentType = json['payment_type'] != null ? PaymentType.fromJson(json['payment_type']) : null;
|
||||
branch = json['branch'] != null ? Branch.fromJson(json['branch']) : null;
|
||||
// NEW: Parsing the transactions list
|
||||
if (json['transactions'] != null) {
|
||||
transactions = [];
|
||||
json['transactions'].forEach((v) {
|
||||
transactions?.add(PaymentsTransaction.fromJson(v));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
num? id;
|
||||
num? businessId;
|
||||
num? partyId;
|
||||
num? userId;
|
||||
num? saleId;
|
||||
num? purchaseId;
|
||||
num? totalDue;
|
||||
num? dueAmountAfterPay;
|
||||
num? payDueAmount;
|
||||
int? paymentTypeId;
|
||||
PaymentType? paymentType;
|
||||
String? invoiceNumber;
|
||||
String? paymentDate;
|
||||
String? createdAt;
|
||||
String? updatedAt;
|
||||
User? user;
|
||||
Party? party;
|
||||
Branch? branch;
|
||||
List<PaymentsTransaction>? transactions; // NEW Variable
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
map['id'] = id;
|
||||
map['business_id'] = businessId;
|
||||
map['party_id'] = partyId;
|
||||
map['user_id'] = userId;
|
||||
map['sale_id'] = saleId;
|
||||
map['purchase_id'] = purchaseId;
|
||||
map['totalDue'] = totalDue;
|
||||
map['dueAmountAfterPay'] = dueAmountAfterPay;
|
||||
map['payDueAmount'] = payDueAmount;
|
||||
map['paymentType'] = paymentType;
|
||||
map['paymentDate'] = paymentDate;
|
||||
map['created_at'] = createdAt;
|
||||
map['updated_at'] = updatedAt;
|
||||
if (user != null) {
|
||||
map['user'] = user?.toJson();
|
||||
}
|
||||
if (party != null) {
|
||||
map['party'] = party?.toJson();
|
||||
}
|
||||
map['branch'] = branch;
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
class PaymentType {
|
||||
int? id;
|
||||
String? name;
|
||||
|
||||
PaymentType({required this.id, required this.name});
|
||||
|
||||
// Factory constructor to create an instance from a Map
|
||||
factory PaymentType.fromJson(Map<String, dynamic> json) {
|
||||
return PaymentType(
|
||||
id: json['id'] as int,
|
||||
name: json['name'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
// Method to convert an instance to a Map
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
29
lib/Screens/Due Calculation/Providers/due_provider.dart
Normal file
29
lib/Screens/Due Calculation/Providers/due_provider.dart
Normal file
@@ -0,0 +1,29 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:mobile_pos/Screens/Due%20Calculation/Model/due_collection_model.dart';
|
||||
|
||||
import '../../../Provider/transactions_provider.dart';
|
||||
import '../Model/due_collection_invoice_model.dart';
|
||||
import '../Repo/due_repo.dart';
|
||||
|
||||
//------------dues-------------------------------------
|
||||
final dueRepo = Provider<DueRepo>((ref) => DueRepo());
|
||||
|
||||
final dueCollectionListProvider = FutureProvider.autoDispose<List<DueCollection>>((ref) {
|
||||
final repo = ref.read(dueRepo);
|
||||
return repo.fetchDueCollectionList();
|
||||
});
|
||||
|
||||
final filteredDueProvider = FutureProvider.family.autoDispose<List<DueCollection>, FilterModel>(
|
||||
(ref, filter) {
|
||||
final repo = ref.read(dueRepo);
|
||||
return repo.fetchDueCollectionList(
|
||||
type: filter.duration,
|
||||
fromDate: filter.fromDate,
|
||||
toDate: filter.toDate,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
DueRepo repo = DueRepo();
|
||||
final dueInvoiceListProvider =
|
||||
FutureProvider.autoDispose.family<DueCollectionInvoice, int>((ref, id) => repo.fetchDueInvoiceList(id: id));
|
||||
132
lib/Screens/Due Calculation/Repo/due_repo.dart
Normal file
132
lib/Screens/Due Calculation/Repo/due_repo.dart
Normal file
@@ -0,0 +1,132 @@
|
||||
// ignore_for_file: 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 '../../../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 '../Model/due_collection_invoice_model.dart';
|
||||
import '../Model/due_collection_model.dart';
|
||||
import '../Providers/due_provider.dart';
|
||||
|
||||
class DueRepo {
|
||||
Future<List<DueCollection>> fetchDueCollectionList({
|
||||
String? type,
|
||||
String? fromDate,
|
||||
String? toDate,
|
||||
}) async {
|
||||
final client = CustomHttpClientGet(client: http.Client());
|
||||
|
||||
// Manually build query string to preserve order
|
||||
final List<String> queryList = [];
|
||||
|
||||
if (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}/dues${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) => DueCollection.fromJson(json)).toList();
|
||||
} else {
|
||||
throw Exception('Failed to fetch Due List. Status code: ${response.statusCode}');
|
||||
}
|
||||
}
|
||||
|
||||
Future<DueCollectionInvoice> fetchDueInvoiceList({required int id}) async {
|
||||
CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client());
|
||||
final uri = Uri.parse('${APIConfig.url}/invoices?party_id=$id');
|
||||
|
||||
final response = await clientGet.get(url: uri);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final parsedData = jsonDecode(response.body);
|
||||
return DueCollectionInvoice.fromJson(parsedData['data']);
|
||||
} else {
|
||||
throw Exception('Failed to fetch Sales List');
|
||||
}
|
||||
}
|
||||
|
||||
Future<DueCollection?> dueCollect({
|
||||
required WidgetRef ref,
|
||||
required BuildContext context,
|
||||
required num partyId,
|
||||
required String? invoiceNumber,
|
||||
required String paymentDate,
|
||||
required List<Map<String, dynamic>> payments,
|
||||
required num payDueAmount,
|
||||
}) async {
|
||||
final uri = Uri.parse('${APIConfig.url}/dues');
|
||||
final requestBody = jsonEncode({
|
||||
'party_id': partyId,
|
||||
'invoiceNumber': invoiceNumber,
|
||||
'paymentDate': paymentDate,
|
||||
'payments': payments,
|
||||
'payDueAmount': payDueAmount,
|
||||
});
|
||||
|
||||
try {
|
||||
CustomHttpClient customHttpClient = CustomHttpClient(client: http.Client(), context: context, ref: ref);
|
||||
var responseData = await customHttpClient.post(
|
||||
url: uri,
|
||||
headers: {
|
||||
"Accept": 'application/json',
|
||||
'Authorization': await getAuthToken(),
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: requestBody);
|
||||
final parsedData = jsonDecode(responseData.body);
|
||||
print("Print Due data: $parsedData");
|
||||
|
||||
if (responseData.statusCode == 200) {
|
||||
EasyLoading.showSuccess('Collected successful!');
|
||||
|
||||
ref.refresh(partiesProvider);
|
||||
|
||||
ref.refresh(purchaseTransactionProvider);
|
||||
ref.refresh(salesTransactionProvider);
|
||||
ref.refresh(businessInfoProvider);
|
||||
ref.refresh(getExpireDateProvider(ref));
|
||||
|
||||
// ref.refresh(dueInvoiceListProvider(partyId.round()));
|
||||
ref.refresh(dueCollectionListProvider);
|
||||
ref.refresh(summaryInfoProvider);
|
||||
|
||||
return DueCollection.fromJson(parsedData['data']);
|
||||
// Navigator.pop(context);
|
||||
// return PurchaseTransaction.fromJson(parsedData);
|
||||
} else {
|
||||
EasyLoading.dismiss().then(
|
||||
(value) => ScaffoldMessenger.of(context)
|
||||
.showSnackBar(SnackBar(content: Text('Due creation failed: ${parsedData['message']}'))),
|
||||
);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
EasyLoading.dismiss().then(
|
||||
(value) => ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('An error occurred: $error'))),
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
475
lib/Screens/Due Calculation/due_collection_screen.dart
Normal file
475
lib/Screens/Due Calculation/due_collection_screen.dart
Normal file
@@ -0,0 +1,475 @@
|
||||
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:mobile_pos/Screens/Due%20Calculation/Model/due_collection_model.dart';
|
||||
import 'package:mobile_pos/Screens/Due%20Calculation/Repo/due_repo.dart';
|
||||
import 'package:mobile_pos/Screens/invoice_details/due_invoice_details.dart';
|
||||
import 'package:mobile_pos/core/theme/_app_colors.dart';
|
||||
import 'package:mobile_pos/generated/l10n.dart' as lang;
|
||||
import 'package:nb_utils/nb_utils.dart';
|
||||
|
||||
import '../../GlobalComponents/glonal_popup.dart';
|
||||
import '../../Provider/profile_provider.dart';
|
||||
import '../../constant.dart';
|
||||
import '../../currency.dart';
|
||||
import '../../widgets/multipal payment mathods/multi_payment_widget.dart';
|
||||
import '../Customers/Model/parties_model.dart';
|
||||
import 'Model/due_collection_invoice_model.dart';
|
||||
import 'Providers/due_provider.dart';
|
||||
|
||||
class DueCollectionScreen extends StatefulWidget {
|
||||
const DueCollectionScreen({super.key, required this.customerModel});
|
||||
|
||||
@override
|
||||
State<DueCollectionScreen> createState() => _DueCollectionScreenState();
|
||||
final Party customerModel;
|
||||
}
|
||||
|
||||
class _DueCollectionScreenState extends State<DueCollectionScreen> {
|
||||
// Key for MultiPaymentWidget
|
||||
final GlobalKey<MultiPaymentWidgetState> paymentWidgetKey = GlobalKey();
|
||||
|
||||
num paidAmount = 0;
|
||||
num remainDueAmount = 0;
|
||||
num dueAmount = 0;
|
||||
|
||||
num calculateDueAmount({required num total}) {
|
||||
if (total < 0) {
|
||||
remainDueAmount = 0;
|
||||
} else {
|
||||
remainDueAmount = dueAmount - total;
|
||||
}
|
||||
return dueAmount - total;
|
||||
}
|
||||
|
||||
TextEditingController paidText = TextEditingController();
|
||||
TextEditingController dateController = TextEditingController(text: DateTime.now().toString().substring(0, 10));
|
||||
DateTime selectedDate = DateTime.now();
|
||||
|
||||
SalesDuesInvoice? selectedInvoice;
|
||||
// int? paymentType; // Removed old single payment type
|
||||
|
||||
// List of items in our dropdown menu
|
||||
int count = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Listener to update state when paidText changes (either manually or via MultiPaymentWidget)
|
||||
paidText.addListener(() {
|
||||
if (paidText.text.isEmpty) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
paidAmount = 0;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
final val = double.tryParse(paidText.text) ?? 0;
|
||||
// Validation: Cannot pay more than due
|
||||
if (val <= dueAmount) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
paidAmount = val;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// If widget pushes value > due, or user types > due
|
||||
// You might want to handle this gracefully.
|
||||
// For now, keeping your old logic:
|
||||
paidText.clear();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
paidAmount = 0;
|
||||
});
|
||||
}
|
||||
EasyLoading.showError(lang.S.of(context).youCanNotPayMoreThenDue);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
count++;
|
||||
return Consumer(builder: (context, consumerRef, __) {
|
||||
final personalData = consumerRef.watch(businessInfoProvider);
|
||||
final dueInvoiceData = consumerRef.watch(dueInvoiceListProvider(widget.customerModel.id?.round() ?? 0));
|
||||
final _theme = Theme.of(context);
|
||||
|
||||
return personalData.when(data: (data) {
|
||||
List<SalesDuesInvoice> items = [];
|
||||
num openingDueAmount = 0;
|
||||
|
||||
return GlobalPopup(
|
||||
child: Scaffold(
|
||||
backgroundColor: kWhite,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
title: Text(
|
||||
lang.S.of(context).collectDue,
|
||||
),
|
||||
centerTitle: true,
|
||||
iconTheme: const IconThemeData(color: Colors.black),
|
||||
elevation: 0.0,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
dueInvoiceData.when(data: (data) {
|
||||
num totalDueInInvoice = 0;
|
||||
if (data.salesDues?.isNotEmpty ?? false) {
|
||||
for (var element in data.salesDues!) {
|
||||
totalDueInInvoice += element.dueAmount ?? 0;
|
||||
items.add(element);
|
||||
}
|
||||
}
|
||||
openingDueAmount = (data.due ?? 0) - totalDueInInvoice;
|
||||
if (selectedInvoice == null) {
|
||||
dueAmount = openingDueAmount;
|
||||
}
|
||||
|
||||
return Expanded(
|
||||
child: DropdownButtonFormField<SalesDuesInvoice>(
|
||||
isExpanded: true,
|
||||
value: selectedInvoice,
|
||||
hint: Text(
|
||||
lang.S.of(context).selectAInvoice,
|
||||
),
|
||||
icon: selectedInvoice != null
|
||||
? GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
selectedInvoice = null;
|
||||
// Reset payment widget when invoice is cleared
|
||||
// paymentWidgetKey.currentState?.clear();
|
||||
});
|
||||
},
|
||||
child: const Icon(
|
||||
Icons.close,
|
||||
color: Colors.red,
|
||||
size: 16,
|
||||
),
|
||||
)
|
||||
: const Icon(Icons.keyboard_arrow_down, color: kGreyTextColor),
|
||||
items: items.map((SalesDuesInvoice invoice) {
|
||||
return DropdownMenuItem(
|
||||
value: invoice,
|
||||
child: Text(
|
||||
invoice.invoiceNumber.toString(),
|
||||
style: _theme.textTheme.bodyMedium,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (newValue) {
|
||||
setState(() {
|
||||
dueAmount = newValue?.dueAmount ?? 0;
|
||||
paidAmount = 0;
|
||||
paidText.clear();
|
||||
selectedInvoice = newValue;
|
||||
// Reset payment widget when invoice changes
|
||||
// paymentWidgetKey.currentState?.clear();
|
||||
});
|
||||
},
|
||||
decoration: const InputDecoration(),
|
||||
),
|
||||
);
|
||||
}, error: (e, stack) {
|
||||
return Text(e.toString());
|
||||
}, loading: () {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
keyboardType: TextInputType.name,
|
||||
readOnly: true,
|
||||
controller: dateController,
|
||||
decoration: InputDecoration(
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
labelText: lang.S.of(context).date,
|
||||
border: const OutlineInputBorder(),
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () async {
|
||||
final DateTime? picked = await showDatePicker(
|
||||
initialDate: DateTime.now(),
|
||||
firstDate: DateTime(2015, 8),
|
||||
lastDate: DateTime(2101),
|
||||
context: context,
|
||||
);
|
||||
if (picked != null) {
|
||||
setState(() {
|
||||
selectedDate = selectedDate.copyWith(
|
||||
year: picked.year,
|
||||
month: picked.month,
|
||||
day: picked.day,
|
||||
);
|
||||
dateController.text = picked.toString().substring(0, 10);
|
||||
});
|
||||
}
|
||||
},
|
||||
icon: const Icon(FeatherIcons.calendar),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
text: "${lang.S.of(context).totalDueAmount}: ",
|
||||
style: _theme.textTheme.bodyMedium?.copyWith(
|
||||
fontSize: 14,
|
||||
color: DAppColors.kSecondary,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: widget.customerModel.due == null
|
||||
? '$currency${0}'
|
||||
: '$currency${widget.customerModel.due!}',
|
||||
style: const TextStyle(color: Color(0xFFFF8C34)),
|
||||
),
|
||||
]),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
TextFormField(
|
||||
keyboardType: TextInputType.name,
|
||||
readOnly: true,
|
||||
initialValue: widget.customerModel.name,
|
||||
decoration: InputDecoration(
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
labelText: lang.S.of(context).customerName,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
///_____Total______________________________
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(5),
|
||||
),
|
||||
color: _theme.colorScheme.primaryContainer,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0xff000000).withValues(alpha: 0.08),
|
||||
spreadRadius: 0,
|
||||
offset: const Offset(0, 4),
|
||||
blurRadius: 24,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xffFEF0F1),
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(5),
|
||||
topLeft: Radius.circular(5),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
lang.S.of(context).totalAmount,
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
Text(
|
||||
dueAmount.toStringAsFixed(2),
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
lang.S.of(context).paidAmount,
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
SizedBox(
|
||||
width: context.width() / 4,
|
||||
height: 30,
|
||||
child: TextFormField(
|
||||
controller: paidText,
|
||||
// Make ReadOnly if multiple payments are selected to avoid conflict
|
||||
readOnly: (paymentWidgetKey.currentState?.getPaymentEntries().length ?? 1) > 1,
|
||||
textAlign: TextAlign.right,
|
||||
decoration: const InputDecoration(
|
||||
hintText: '0',
|
||||
hintStyle: TextStyle(color: kNeutralColor),
|
||||
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.all(10.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
lang.S.of(context).dueAmount,
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
Text(
|
||||
calculateDueAmount(total: paidAmount).toStringAsFixed(2),
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
///__________Payment_Type_Widget_______________________________________
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Column(
|
||||
children: [
|
||||
const Divider(height: 20),
|
||||
MultiPaymentWidget(
|
||||
key: paymentWidgetKey,
|
||||
showWalletOption: true, // Configure as needed
|
||||
showChequeOption: (widget.customerModel.type != 'Supplier'), // Configure as needed
|
||||
totalAmountController: paidText,
|
||||
onPaymentListChanged: () {},
|
||||
),
|
||||
const Divider(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16),
|
||||
child: 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 {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
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 (paidAmount > 0 && dueAmount > 0) {
|
||||
// Get payments from widget
|
||||
List<PaymentEntry> payments = paymentWidgetKey.currentState?.getPaymentEntries() ?? [];
|
||||
|
||||
if (payments.isEmpty) {
|
||||
EasyLoading.showError(lang.S.of(context).noDueSelected); // Or "Please select payment"
|
||||
} else {
|
||||
EasyLoading.show();
|
||||
|
||||
// Serialize Payment List
|
||||
List<Map<String, dynamic>> paymentData = payments.map((e) => e.toJson()).toList();
|
||||
|
||||
DueRepo repo = DueRepo();
|
||||
DueCollection? dueData;
|
||||
dueData = await repo.dueCollect(
|
||||
ref: consumerRef,
|
||||
context: context,
|
||||
partyId: widget.customerModel.id ?? 0,
|
||||
invoiceNumber: selectedInvoice?.invoiceNumber,
|
||||
paymentDate: selectedDate.toIso8601String(),
|
||||
payments: paymentData,
|
||||
payDueAmount: paidAmount,
|
||||
);
|
||||
|
||||
if (dueData != null) {
|
||||
DueInvoiceDetails(
|
||||
dueCollection: dueData,
|
||||
personalInformationModel: data,
|
||||
isFromDue: true,
|
||||
).launch(context);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
EasyLoading.showError(
|
||||
lang.S.of(context).noDueSelected,
|
||||
);
|
||||
}
|
||||
},
|
||||
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());
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
200
lib/Screens/Due Calculation/due_list_screen.dart
Normal file
200
lib/Screens/Due Calculation/due_list_screen.dart
Normal file
@@ -0,0 +1,200 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:iconly/iconly.dart';
|
||||
import 'package:mobile_pos/Provider/profile_provider.dart';
|
||||
import 'package:mobile_pos/Screens/Customers/Model/parties_model.dart';
|
||||
import 'package:mobile_pos/Screens/Due%20Calculation/due_collection_screen.dart';
|
||||
import 'package:mobile_pos/generated/l10n.dart' as lang;
|
||||
import 'package:nb_utils/nb_utils.dart';
|
||||
|
||||
import '../../Const/api_config.dart';
|
||||
import '../../GlobalComponents/glonal_popup.dart';
|
||||
import '../../constant.dart' as DAppColors;
|
||||
import '../../constant.dart';
|
||||
import '../../currency.dart';
|
||||
import '../../http_client/custome_http_client.dart';
|
||||
import '../../widgets/empty_widget/_empty_widget.dart';
|
||||
import '../Customers/Provider/customer_provider.dart';
|
||||
import '../../service/check_user_role_permission_provider.dart';
|
||||
|
||||
class DueCalculationContactScreen extends StatefulWidget {
|
||||
const DueCalculationContactScreen({super.key});
|
||||
|
||||
@override
|
||||
State<DueCalculationContactScreen> createState() => _DueCalculationContactScreenState();
|
||||
}
|
||||
|
||||
class _DueCalculationContactScreenState extends State<DueCalculationContactScreen> {
|
||||
late Color color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final _theme = Theme.of(context);
|
||||
return GlobalPopup(
|
||||
child: Scaffold(
|
||||
backgroundColor: kWhite,
|
||||
resizeToAvoidBottomInset: true,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
title: Text(
|
||||
lang.S.of(context).dueList,
|
||||
),
|
||||
centerTitle: true,
|
||||
iconTheme: const IconThemeData(color: Colors.black),
|
||||
elevation: 0.0,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Consumer(builder: (context, ref, __) {
|
||||
final providerData = ref.watch(partiesProvider);
|
||||
final businessInfo = ref.watch(businessInfoProvider);
|
||||
final permissionService = PermissionService(ref);
|
||||
return providerData.when(data: (parties) {
|
||||
List<Party> dueCustomerList = [];
|
||||
|
||||
for (var party in parties) {
|
||||
if ((party.due ?? 0) > 0) {
|
||||
dueCustomerList.add(party);
|
||||
}
|
||||
}
|
||||
return dueCustomerList.isNotEmpty
|
||||
? businessInfo.when(data: (details) {
|
||||
if (!permissionService.hasPermission(Permit.duesRead.value)) {
|
||||
return Center(child: PermitDenyWidget());
|
||||
}
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: dueCustomerList.length,
|
||||
itemBuilder: (_, index) {
|
||||
dueCustomerList[index].type == 'Retailer' ? color = const Color(0xFF56da87) : Colors.white;
|
||||
dueCustomerList[index].type == 'Wholesaler'
|
||||
? color = const Color(0xFF25a9e0)
|
||||
: Colors.white;
|
||||
dueCustomerList[index].type == 'Dealer' ? color = const Color(0xFFff5f00) : Colors.white;
|
||||
dueCustomerList[index].type == 'Supplier' ? color = const Color(0xFFA569BD) : Colors.white;
|
||||
|
||||
final item = dueCustomerList[index];
|
||||
final normalizedType = (item.type ?? '').toLowerCase();
|
||||
|
||||
String effectiveDisplayType;
|
||||
|
||||
if (normalizedType == 'retailer') {
|
||||
effectiveDisplayType = lang.S.of(context).customer;
|
||||
} else if (normalizedType == 'wholesaler') {
|
||||
effectiveDisplayType = lang.S.of(context).wholesaler;
|
||||
} else if (normalizedType == 'dealer') {
|
||||
effectiveDisplayType = lang.S.of(context).dealer;
|
||||
} else if (normalizedType == 'supplier') {
|
||||
effectiveDisplayType = lang.S.of(context).supplier;
|
||||
} else {
|
||||
effectiveDisplayType = item.type ?? '';
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
visualDensity: const VisualDensity(vertical: -2),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
onTap: () async {
|
||||
DueCollectionScreen(customerModel: dueCustomerList[index]).launch(context);
|
||||
},
|
||||
leading: dueCustomerList[index].image != null
|
||||
? Container(
|
||||
height: 40,
|
||||
width: 40,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: DAppColors.kBorder, width: 0.3),
|
||||
image: DecorationImage(
|
||||
image: NetworkImage(
|
||||
'${APIConfig.domain}${dueCustomerList[index].image ?? ''}',
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
)
|
||||
: CircleAvatarWidget(name: dueCustomerList[index].name ?? 'n/a'),
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
dueCustomerList[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 ${dueCustomerList[index].due}',
|
||||
style: _theme.textTheme.bodyMedium?.copyWith(
|
||||
fontSize: 16.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
// dueCustomerList[index].type ?? '',
|
||||
effectiveDisplayType,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: _theme.textTheme.bodyMedium?.copyWith(
|
||||
color: color,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
dueCustomerList[index].due != null && dueCustomerList[index].due != 0
|
||||
? lang.S.of(context).due
|
||||
: 'No Due',
|
||||
style: _theme.textTheme.bodyMedium?.copyWith(
|
||||
color: dueCustomerList[index].due != null && dueCustomerList[index].due != 0
|
||||
? const Color(0xFFff5f00)
|
||||
: const Color(0xff808191),
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: const Icon(
|
||||
IconlyLight.arrow_right_2,
|
||||
size: 18,
|
||||
),
|
||||
);
|
||||
});
|
||||
}, error: (e, stack) {
|
||||
return const CircularProgressIndicator();
|
||||
}, loading: () {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
})
|
||||
: Center(
|
||||
child: Text(
|
||||
lang.S.of(context).noDataAvailabe,
|
||||
maxLines: 2,
|
||||
style: const TextStyle(color: Colors.black, fontWeight: FontWeight.bold, fontSize: 20.0),
|
||||
),
|
||||
);
|
||||
}, error: (e, stack) {
|
||||
return Text(e.toString());
|
||||
}, loading: () {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
});
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user