first commit

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

View File

@@ -0,0 +1,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;
}
}

View 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;
}

View 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));

View 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;
}
}
}

View 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());
});
});
}
}

View 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());
});
}),
),
),
);
}
}