From 2c98ac359c42033cb7374b80b630506198f52ef3 Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Fri, 21 Nov 2025 16:42:33 +0530 Subject: [PATCH] feat: enhance payment request data models for null safety and improve filter options handling in PaymentRequestController --- .../finance/payment_request_controller.dart | 33 ++-- .../finance/payment_request_list_model.dart | 154 ++++++++++-------- lib/view/finance/payment_request_screen.dart | 31 ++-- 3 files changed, 123 insertions(+), 95 deletions(-) diff --git a/lib/controller/finance/payment_request_controller.dart b/lib/controller/finance/payment_request_controller.dart index 63bd234..3418c83 100644 --- a/lib/controller/finance/payment_request_controller.dart +++ b/lib/controller/finance/payment_request_controller.dart @@ -32,13 +32,14 @@ class PaymentRequestController extends GetxController { Future fetchPaymentRequestFilterOptions() async { try { final response = await ApiService.getExpensePaymentRequestFilterApi(); - if (response != null) { - projects.assignAll(response.data.projects); - payees.assignAll(response.data.payees); - categories.assignAll(response.data.expenseCategory); - currencies.assignAll(response.data.currency); - statuses.assignAll(response.data.status); - createdBy.assignAll(response.data.createdBy); + + if (response != null && response.data != null) { + projects.assignAll(response.data!.projects ?? []); + payees.assignAll(response.data!.payees ?? []); + categories.assignAll(response.data!.expenseCategory ?? []); + currencies.assignAll(response.data!.currency ?? []); + statuses.assignAll(response.data!.status ?? []); + createdBy.assignAll(response.data!.createdBy ?? []); } else { logSafe("Payment request filter API returned null", level: LogLevel.warning); @@ -63,7 +64,7 @@ class PaymentRequestController extends GetxController { isLoading.value = false; } -// ---------------- Load More ---------------- + // ---------------- Load More ---------------- Future loadMorePaymentRequests() async { if (isLoading.value || !_hasMoreData) return; @@ -74,7 +75,7 @@ class PaymentRequestController extends GetxController { isLoading.value = false; } -// ---------------- Internal API Call ---------------- + // ---------------- Internal API Call ---------------- Future _fetchPaymentRequestsFromApi() async { try { final response = await ApiService.getExpensePaymentRequestListApi( @@ -84,17 +85,17 @@ class PaymentRequestController extends GetxController { searchString: searchString.value, ); - if (response != null && response.data.data.isNotEmpty) { + final data = response?.data; + final reqList = data?.data ?? []; + + if (response != null && data != null && reqList.isNotEmpty) { if (_pageNumber == 1) { - // First page, replace the list - paymentRequests.assignAll(response.data.data); + paymentRequests.assignAll(reqList); } else { - // Append next page items at the end - paymentRequests.addAll(response.data.data); + paymentRequests.addAll(reqList); } - // If returned data is less than page size, no more data - if (response.data.data.length < _pageSize) { + if (reqList.length < _pageSize) { _hasMoreData = false; } } else { diff --git a/lib/model/finance/payment_request_list_model.dart b/lib/model/finance/payment_request_list_model.dart index 538ccd4..42c2cc7 100644 --- a/lib/model/finance/payment_request_list_model.dart +++ b/lib/model/finance/payment_request_list_model.dart @@ -13,21 +13,23 @@ class PaymentRequestResponse { required this.data, }); - bool success; - String message; - PaymentRequestData data; + bool? success; + String? message; + PaymentRequestData? data; factory PaymentRequestResponse.fromJson(Map json) => PaymentRequestResponse( success: json["success"], message: json["message"], - data: PaymentRequestData.fromJson(json["data"]), + data: json["data"] != null + ? PaymentRequestData.fromJson(json["data"]) + : null, ); Map toJson() => { "success": success, "message": message, - "data": data.toJson(), + "data": data?.toJson(), }; } @@ -39,25 +41,27 @@ class PaymentRequestData { required this.data, }); - int currentPage; - int totalPages; - int totalEntities; - List data; + int? currentPage; + int? totalPages; + int? totalEntities; + List? data; factory PaymentRequestData.fromJson(Map json) => PaymentRequestData( currentPage: json["currentPage"], totalPages: json["totalPages"], totalEntities: json["totalEntities"], - data: List.from( - json["data"].map((x) => PaymentRequest.fromJson(x))), + data: json["data"] != null + ? List.from( + json["data"].map((x) => PaymentRequest.fromJson(x))) + : [], ); Map toJson() => { "currentPage": currentPage, "totalPages": totalPages, "totalEntities": totalEntities, - "data": List.from(data.map((x) => x.toJson())), + "data": data?.map((x) => x.toJson()).toList(), }; } @@ -82,23 +86,23 @@ class PaymentRequest { required this.isExpenseCreated, }); - String id; - String title; - String description; + String? id; + String? title; + String? description; dynamic recurringPayment; - String paymentRequestUID; - String payee; - Currency currency; - num amount; - DateTime dueDate; - Project project; - ExpenseCategory expenseCategory; - ExpenseStatus expenseStatus; - bool isAdvancePayment; - DateTime createdAt; - CreatedBy createdBy; - bool isActive; - bool isExpenseCreated; + String? paymentRequestUID; + String? payee; + Currency? currency; + num? amount; + DateTime? dueDate; + Project? project; + ExpenseCategory? expenseCategory; + ExpenseStatus? expenseStatus; + bool? isAdvancePayment; + DateTime? createdAt; + CreatedBy? createdBy; + bool? isActive; + bool? isExpenseCreated; factory PaymentRequest.fromJson(Map json) => PaymentRequest( id: json["id"], @@ -107,15 +111,28 @@ class PaymentRequest { recurringPayment: json["recurringPayment"], paymentRequestUID: json["paymentRequestUID"], payee: json["payee"], - currency: Currency.fromJson(json["currency"]), + currency: json["currency"] != null + ? Currency.fromJson(json["currency"]) + : null, amount: json["amount"], - dueDate: DateTime.parse(json["dueDate"]), - project: Project.fromJson(json["project"]), - expenseCategory: ExpenseCategory.fromJson(json["expenseCategory"]), - expenseStatus: ExpenseStatus.fromJson(json["expenseStatus"]), + dueDate: json["dueDate"] != null + ? DateTime.parse(json["dueDate"]) + : null, + project: + json["project"] != null ? Project.fromJson(json["project"]) : null, + expenseCategory: json["expenseCategory"] != null + ? ExpenseCategory.fromJson(json["expenseCategory"]) + : null, + expenseStatus: json["expenseStatus"] != null + ? ExpenseStatus.fromJson(json["expenseStatus"]) + : null, isAdvancePayment: json["isAdvancePayment"], - createdAt: DateTime.parse(json["createdAt"]), - createdBy: CreatedBy.fromJson(json["createdBy"]), + createdAt: json["createdAt"] != null + ? DateTime.parse(json["createdAt"]) + : null, + createdBy: json["createdBy"] != null + ? CreatedBy.fromJson(json["createdBy"]) + : null, isActive: json["isActive"], isExpenseCreated: json["isExpenseCreated"], ); @@ -127,15 +144,15 @@ class PaymentRequest { "recurringPayment": recurringPayment, "paymentRequestUID": paymentRequestUID, "payee": payee, - "currency": currency.toJson(), + "currency": currency?.toJson(), "amount": amount, - "dueDate": dueDate.toIso8601String(), - "project": project.toJson(), - "expenseCategory": expenseCategory.toJson(), - "expenseStatus": expenseStatus.toJson(), + "dueDate": dueDate?.toIso8601String(), + "project": project?.toJson(), + "expenseCategory": expenseCategory?.toJson(), + "expenseStatus": expenseStatus?.toJson(), "isAdvancePayment": isAdvancePayment, - "createdAt": createdAt.toIso8601String(), - "createdBy": createdBy.toJson(), + "createdAt": createdAt?.toIso8601String(), + "createdBy": createdBy?.toJson(), "isActive": isActive, "isExpenseCreated": isExpenseCreated, }; @@ -150,11 +167,11 @@ class Currency { required this.isActive, }); - String id; - String currencyCode; - String currencyName; - String symbol; - bool isActive; + String? id; + String? currencyCode; + String? currencyName; + String? symbol; + bool? isActive; factory Currency.fromJson(Map json) => Currency( id: json["id"], @@ -179,8 +196,8 @@ class Project { required this.name, }); - String id; - String name; + String? id; + String? name; factory Project.fromJson(Map json) => Project( id: json["id"], @@ -202,13 +219,14 @@ class ExpenseCategory { required this.description, }); - String id; - String name; - bool noOfPersonsRequired; - bool isAttachmentRequried; - String description; + String? id; + String? name; + bool? noOfPersonsRequired; + bool? isAttachmentRequried; + String? description; - factory ExpenseCategory.fromJson(Map json) => ExpenseCategory( + factory ExpenseCategory.fromJson(Map json) => + ExpenseCategory( id: json["id"], name: json["name"], noOfPersonsRequired: json["noOfPersonsRequired"], @@ -236,13 +254,13 @@ class ExpenseStatus { required this.isSystem, }); - String id; - String name; - String displayName; - String description; + String? id; + String? name; + String? displayName; + String? description; dynamic permissionIds; - String color; - bool isSystem; + String? color; + bool? isSystem; factory ExpenseStatus.fromJson(Map json) => ExpenseStatus( id: json["id"], @@ -276,13 +294,13 @@ class CreatedBy { required this.jobRoleName, }); - String id; - String firstName; - String lastName; - String email; - String photo; - String jobRoleId; - String jobRoleName; + String? id; + String? firstName; + String? lastName; + String? email; + String? photo; + String? jobRoleId; + String? jobRoleName; factory CreatedBy.fromJson(Map json) => CreatedBy( id: json["id"], diff --git a/lib/view/finance/payment_request_screen.dart b/lib/view/finance/payment_request_screen.dart index 7374376..0952184 100644 --- a/lib/view/finance/payment_request_screen.dart +++ b/lib/view/finance/payment_request_screen.dart @@ -14,7 +14,6 @@ import 'package:marco/controller/permission_controller.dart'; import 'package:marco/helpers/utils/permission_constants.dart'; import 'package:marco/helpers/widgets/my_refresh_indicator.dart'; - class PaymentRequestMainScreen extends StatefulWidget { const PaymentRequestMainScreen({super.key}); @@ -70,19 +69,29 @@ class _PaymentRequestMainScreenState extends State final now = DateTime.now(); final filtered = paymentController.paymentRequests.where((e) { - return query.isEmpty || - e.title.toLowerCase().contains(query) || - e.payee.toLowerCase().contains(query); + final title = e.title?.toLowerCase() ?? ""; + final payee = e.payee?.toLowerCase() ?? ""; + + return query.isEmpty || title.contains(query) || payee.contains(query); }).toList() - ..sort((a, b) => b.dueDate.compareTo(a.dueDate)); + ..sort((a, b) { + final aDate = a.dueDate ?? DateTime(1900); + final bDate = b.dueDate ?? DateTime(1900); + return bDate.compareTo(aDate); + }); + + DateTime startOfMonth = DateTime(now.year, now.month, 1); + DateTime previousMonthEnd = DateTime(now.year, now.month, 0); return isHistory - ? filtered - .where((e) => e.dueDate.isBefore(DateTime(now.year, now.month, 1))) - .toList() - : filtered - .where((e) => e.dueDate.isAfter(DateTime(now.year, now.month, 0))) - .toList(); + ? filtered.where((e) { + final d = e.dueDate; + return d != null && d.isBefore(startOfMonth); + }).toList() + : filtered.where((e) { + final d = e.dueDate; + return d != null && d.isAfter(previousMonthEnd); + }).toList(); } @override