feat: enhance payment request data models for null safety and improve filter options handling in PaymentRequestController

This commit is contained in:
Vaibhav Surve 2025-11-21 16:42:33 +05:30
parent fc78806af2
commit 2c98ac359c
3 changed files with 123 additions and 95 deletions

View File

@ -32,13 +32,14 @@ class PaymentRequestController extends GetxController {
Future<void> fetchPaymentRequestFilterOptions() async { Future<void> fetchPaymentRequestFilterOptions() async {
try { try {
final response = await ApiService.getExpensePaymentRequestFilterApi(); final response = await ApiService.getExpensePaymentRequestFilterApi();
if (response != null) {
projects.assignAll(response.data.projects); if (response != null && response.data != null) {
payees.assignAll(response.data.payees); projects.assignAll(response.data!.projects ?? []);
categories.assignAll(response.data.expenseCategory); payees.assignAll(response.data!.payees ?? []);
currencies.assignAll(response.data.currency); categories.assignAll(response.data!.expenseCategory ?? []);
statuses.assignAll(response.data.status); currencies.assignAll(response.data!.currency ?? []);
createdBy.assignAll(response.data.createdBy); statuses.assignAll(response.data!.status ?? []);
createdBy.assignAll(response.data!.createdBy ?? []);
} else { } else {
logSafe("Payment request filter API returned null", logSafe("Payment request filter API returned null",
level: LogLevel.warning); level: LogLevel.warning);
@ -84,17 +85,17 @@ class PaymentRequestController extends GetxController {
searchString: searchString.value, 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) { if (_pageNumber == 1) {
// First page, replace the list paymentRequests.assignAll(reqList);
paymentRequests.assignAll(response.data.data);
} else { } else {
// Append next page items at the end paymentRequests.addAll(reqList);
paymentRequests.addAll(response.data.data);
} }
// If returned data is less than page size, no more data if (reqList.length < _pageSize) {
if (response.data.data.length < _pageSize) {
_hasMoreData = false; _hasMoreData = false;
} }
} else { } else {

View File

@ -13,21 +13,23 @@ class PaymentRequestResponse {
required this.data, required this.data,
}); });
bool success; bool? success;
String message; String? message;
PaymentRequestData data; PaymentRequestData? data;
factory PaymentRequestResponse.fromJson(Map<String, dynamic> json) => factory PaymentRequestResponse.fromJson(Map<String, dynamic> json) =>
PaymentRequestResponse( PaymentRequestResponse(
success: json["success"], success: json["success"],
message: json["message"], message: json["message"],
data: PaymentRequestData.fromJson(json["data"]), data: json["data"] != null
? PaymentRequestData.fromJson(json["data"])
: null,
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
"success": success, "success": success,
"message": message, "message": message,
"data": data.toJson(), "data": data?.toJson(),
}; };
} }
@ -39,25 +41,27 @@ class PaymentRequestData {
required this.data, required this.data,
}); });
int currentPage; int? currentPage;
int totalPages; int? totalPages;
int totalEntities; int? totalEntities;
List<PaymentRequest> data; List<PaymentRequest>? data;
factory PaymentRequestData.fromJson(Map<String, dynamic> json) => factory PaymentRequestData.fromJson(Map<String, dynamic> json) =>
PaymentRequestData( PaymentRequestData(
currentPage: json["currentPage"], currentPage: json["currentPage"],
totalPages: json["totalPages"], totalPages: json["totalPages"],
totalEntities: json["totalEntities"], totalEntities: json["totalEntities"],
data: List<PaymentRequest>.from( data: json["data"] != null
json["data"].map((x) => PaymentRequest.fromJson(x))), ? List<PaymentRequest>.from(
json["data"].map((x) => PaymentRequest.fromJson(x)))
: [],
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
"currentPage": currentPage, "currentPage": currentPage,
"totalPages": totalPages, "totalPages": totalPages,
"totalEntities": totalEntities, "totalEntities": totalEntities,
"data": List<dynamic>.from(data.map((x) => x.toJson())), "data": data?.map((x) => x.toJson()).toList(),
}; };
} }
@ -82,23 +86,23 @@ class PaymentRequest {
required this.isExpenseCreated, required this.isExpenseCreated,
}); });
String id; String? id;
String title; String? title;
String description; String? description;
dynamic recurringPayment; dynamic recurringPayment;
String paymentRequestUID; String? paymentRequestUID;
String payee; String? payee;
Currency currency; Currency? currency;
num amount; num? amount;
DateTime dueDate; DateTime? dueDate;
Project project; Project? project;
ExpenseCategory expenseCategory; ExpenseCategory? expenseCategory;
ExpenseStatus expenseStatus; ExpenseStatus? expenseStatus;
bool isAdvancePayment; bool? isAdvancePayment;
DateTime createdAt; DateTime? createdAt;
CreatedBy createdBy; CreatedBy? createdBy;
bool isActive; bool? isActive;
bool isExpenseCreated; bool? isExpenseCreated;
factory PaymentRequest.fromJson(Map<String, dynamic> json) => PaymentRequest( factory PaymentRequest.fromJson(Map<String, dynamic> json) => PaymentRequest(
id: json["id"], id: json["id"],
@ -107,15 +111,28 @@ class PaymentRequest {
recurringPayment: json["recurringPayment"], recurringPayment: json["recurringPayment"],
paymentRequestUID: json["paymentRequestUID"], paymentRequestUID: json["paymentRequestUID"],
payee: json["payee"], payee: json["payee"],
currency: Currency.fromJson(json["currency"]), currency: json["currency"] != null
? Currency.fromJson(json["currency"])
: null,
amount: json["amount"], amount: json["amount"],
dueDate: DateTime.parse(json["dueDate"]), dueDate: json["dueDate"] != null
project: Project.fromJson(json["project"]), ? DateTime.parse(json["dueDate"])
expenseCategory: ExpenseCategory.fromJson(json["expenseCategory"]), : null,
expenseStatus: ExpenseStatus.fromJson(json["expenseStatus"]), 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"], isAdvancePayment: json["isAdvancePayment"],
createdAt: DateTime.parse(json["createdAt"]), createdAt: json["createdAt"] != null
createdBy: CreatedBy.fromJson(json["createdBy"]), ? DateTime.parse(json["createdAt"])
: null,
createdBy: json["createdBy"] != null
? CreatedBy.fromJson(json["createdBy"])
: null,
isActive: json["isActive"], isActive: json["isActive"],
isExpenseCreated: json["isExpenseCreated"], isExpenseCreated: json["isExpenseCreated"],
); );
@ -127,15 +144,15 @@ class PaymentRequest {
"recurringPayment": recurringPayment, "recurringPayment": recurringPayment,
"paymentRequestUID": paymentRequestUID, "paymentRequestUID": paymentRequestUID,
"payee": payee, "payee": payee,
"currency": currency.toJson(), "currency": currency?.toJson(),
"amount": amount, "amount": amount,
"dueDate": dueDate.toIso8601String(), "dueDate": dueDate?.toIso8601String(),
"project": project.toJson(), "project": project?.toJson(),
"expenseCategory": expenseCategory.toJson(), "expenseCategory": expenseCategory?.toJson(),
"expenseStatus": expenseStatus.toJson(), "expenseStatus": expenseStatus?.toJson(),
"isAdvancePayment": isAdvancePayment, "isAdvancePayment": isAdvancePayment,
"createdAt": createdAt.toIso8601String(), "createdAt": createdAt?.toIso8601String(),
"createdBy": createdBy.toJson(), "createdBy": createdBy?.toJson(),
"isActive": isActive, "isActive": isActive,
"isExpenseCreated": isExpenseCreated, "isExpenseCreated": isExpenseCreated,
}; };
@ -150,11 +167,11 @@ class Currency {
required this.isActive, required this.isActive,
}); });
String id; String? id;
String currencyCode; String? currencyCode;
String currencyName; String? currencyName;
String symbol; String? symbol;
bool isActive; bool? isActive;
factory Currency.fromJson(Map<String, dynamic> json) => Currency( factory Currency.fromJson(Map<String, dynamic> json) => Currency(
id: json["id"], id: json["id"],
@ -179,8 +196,8 @@ class Project {
required this.name, required this.name,
}); });
String id; String? id;
String name; String? name;
factory Project.fromJson(Map<String, dynamic> json) => Project( factory Project.fromJson(Map<String, dynamic> json) => Project(
id: json["id"], id: json["id"],
@ -202,13 +219,14 @@ class ExpenseCategory {
required this.description, required this.description,
}); });
String id; String? id;
String name; String? name;
bool noOfPersonsRequired; bool? noOfPersonsRequired;
bool isAttachmentRequried; bool? isAttachmentRequried;
String description; String? description;
factory ExpenseCategory.fromJson(Map<String, dynamic> json) => ExpenseCategory( factory ExpenseCategory.fromJson(Map<String, dynamic> json) =>
ExpenseCategory(
id: json["id"], id: json["id"],
name: json["name"], name: json["name"],
noOfPersonsRequired: json["noOfPersonsRequired"], noOfPersonsRequired: json["noOfPersonsRequired"],
@ -236,13 +254,13 @@ class ExpenseStatus {
required this.isSystem, required this.isSystem,
}); });
String id; String? id;
String name; String? name;
String displayName; String? displayName;
String description; String? description;
dynamic permissionIds; dynamic permissionIds;
String color; String? color;
bool isSystem; bool? isSystem;
factory ExpenseStatus.fromJson(Map<String, dynamic> json) => ExpenseStatus( factory ExpenseStatus.fromJson(Map<String, dynamic> json) => ExpenseStatus(
id: json["id"], id: json["id"],
@ -276,13 +294,13 @@ class CreatedBy {
required this.jobRoleName, required this.jobRoleName,
}); });
String id; String? id;
String firstName; String? firstName;
String lastName; String? lastName;
String email; String? email;
String photo; String? photo;
String jobRoleId; String? jobRoleId;
String jobRoleName; String? jobRoleName;
factory CreatedBy.fromJson(Map<String, dynamic> json) => CreatedBy( factory CreatedBy.fromJson(Map<String, dynamic> json) => CreatedBy(
id: json["id"], id: json["id"],

View File

@ -14,7 +14,6 @@ import 'package:marco/controller/permission_controller.dart';
import 'package:marco/helpers/utils/permission_constants.dart'; import 'package:marco/helpers/utils/permission_constants.dart';
import 'package:marco/helpers/widgets/my_refresh_indicator.dart'; import 'package:marco/helpers/widgets/my_refresh_indicator.dart';
class PaymentRequestMainScreen extends StatefulWidget { class PaymentRequestMainScreen extends StatefulWidget {
const PaymentRequestMainScreen({super.key}); const PaymentRequestMainScreen({super.key});
@ -70,19 +69,29 @@ class _PaymentRequestMainScreenState extends State<PaymentRequestMainScreen>
final now = DateTime.now(); final now = DateTime.now();
final filtered = paymentController.paymentRequests.where((e) { final filtered = paymentController.paymentRequests.where((e) {
return query.isEmpty || final title = e.title?.toLowerCase() ?? "";
e.title.toLowerCase().contains(query) || final payee = e.payee?.toLowerCase() ?? "";
e.payee.toLowerCase().contains(query);
return query.isEmpty || title.contains(query) || payee.contains(query);
}).toList() }).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 return isHistory
? filtered ? filtered.where((e) {
.where((e) => e.dueDate.isBefore(DateTime(now.year, now.month, 1))) final d = e.dueDate;
.toList() return d != null && d.isBefore(startOfMonth);
: filtered }).toList()
.where((e) => e.dueDate.isAfter(DateTime(now.year, now.month, 0))) : filtered.where((e) {
.toList(); final d = e.dueDate;
return d != null && d.isAfter(previousMonthEnd);
}).toList();
} }
@override @override