resoled payment request model crash issue
This commit is contained in:
parent
618ac6f27a
commit
dc4ea7979c
@ -1,30 +1,32 @@
|
||||
class PaymentRequestDetail {
|
||||
bool success;
|
||||
String message;
|
||||
PaymentRequestData? data;
|
||||
dynamic errors;
|
||||
int statusCode;
|
||||
DateTime timestamp;
|
||||
final bool? success;
|
||||
final String? message;
|
||||
final PaymentRequestData? data;
|
||||
final dynamic errors;
|
||||
final int? statusCode;
|
||||
final DateTime? timestamp;
|
||||
|
||||
PaymentRequestDetail({
|
||||
required this.success,
|
||||
required this.message,
|
||||
this.success,
|
||||
this.message,
|
||||
this.data,
|
||||
this.errors,
|
||||
required this.statusCode,
|
||||
required this.timestamp,
|
||||
this.statusCode,
|
||||
this.timestamp,
|
||||
});
|
||||
|
||||
factory PaymentRequestDetail.fromJson(Map<String, dynamic> json) =>
|
||||
PaymentRequestDetail(
|
||||
success: json['success'],
|
||||
message: json['message'],
|
||||
success: json['success'] as bool?,
|
||||
message: json['message'] as String?,
|
||||
data: json['data'] != null
|
||||
? PaymentRequestData.fromJson(json['data'])
|
||||
: null,
|
||||
errors: json['errors'],
|
||||
statusCode: json['statusCode'],
|
||||
timestamp: DateTime.parse(json['timestamp']),
|
||||
statusCode: json['statusCode'] as int?,
|
||||
timestamp: json['timestamp'] != null
|
||||
? DateTime.tryParse(json['timestamp'])
|
||||
: null,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
@ -33,117 +35,129 @@ class PaymentRequestDetail {
|
||||
'data': data?.toJson(),
|
||||
'errors': errors,
|
||||
'statusCode': statusCode,
|
||||
'timestamp': timestamp.toIso8601String(),
|
||||
'timestamp': timestamp?.toIso8601String(),
|
||||
};
|
||||
}
|
||||
|
||||
class PaymentRequestData {
|
||||
String id;
|
||||
String title;
|
||||
String description;
|
||||
String paymentRequestUID;
|
||||
String payee;
|
||||
Currency currency;
|
||||
double amount;
|
||||
double? baseAmount;
|
||||
double? taxAmount;
|
||||
DateTime dueDate;
|
||||
Project project;
|
||||
dynamic recurringPayment;
|
||||
ExpenseCategory expenseCategory;
|
||||
ExpenseStatus expenseStatus;
|
||||
String? paidTransactionId;
|
||||
DateTime? paidAt;
|
||||
User? paidBy;
|
||||
bool isAdvancePayment;
|
||||
DateTime createdAt;
|
||||
User createdBy;
|
||||
DateTime updatedAt;
|
||||
User? updatedBy;
|
||||
List<NextStatus> nextStatus;
|
||||
List<UpdateLog> updateLogs;
|
||||
List<Attachment> attachments;
|
||||
bool isActive;
|
||||
bool isExpenseCreated;
|
||||
final String? id;
|
||||
final String? title;
|
||||
final String? description;
|
||||
final String? paymentRequestUID;
|
||||
final String? payee;
|
||||
final Currency? currency;
|
||||
final double? amount;
|
||||
final double? baseAmount;
|
||||
final double? taxAmount;
|
||||
final DateTime? dueDate;
|
||||
final Project? project;
|
||||
final RecurringPayment? recurringPayment;
|
||||
final ExpenseCategory? expenseCategory;
|
||||
final ExpenseStatus? expenseStatus;
|
||||
final String? paidTransactionId;
|
||||
final DateTime? paidAt;
|
||||
final User? paidBy;
|
||||
final bool? isAdvancePayment;
|
||||
final DateTime? createdAt;
|
||||
final User? createdBy;
|
||||
final DateTime? updatedAt;
|
||||
final User? updatedBy;
|
||||
final List<NextStatus>? nextStatus;
|
||||
final List<UpdateLog>? updateLogs;
|
||||
final List<Attachment>? attachments;
|
||||
final bool? isActive;
|
||||
final bool? isExpenseCreated;
|
||||
|
||||
PaymentRequestData({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.paymentRequestUID,
|
||||
required this.payee,
|
||||
required this.currency,
|
||||
required this.amount,
|
||||
this.id,
|
||||
this.title,
|
||||
this.description,
|
||||
this.paymentRequestUID,
|
||||
this.payee,
|
||||
this.currency,
|
||||
this.amount,
|
||||
this.baseAmount,
|
||||
this.taxAmount,
|
||||
required this.dueDate,
|
||||
required this.project,
|
||||
this.dueDate,
|
||||
this.project,
|
||||
this.recurringPayment,
|
||||
required this.expenseCategory,
|
||||
required this.expenseStatus,
|
||||
this.expenseCategory,
|
||||
this.expenseStatus,
|
||||
this.paidTransactionId,
|
||||
this.paidAt,
|
||||
this.paidBy,
|
||||
required this.isAdvancePayment,
|
||||
required this.createdAt,
|
||||
required this.createdBy,
|
||||
required this.updatedAt,
|
||||
this.isAdvancePayment,
|
||||
this.createdAt,
|
||||
this.createdBy,
|
||||
this.updatedAt,
|
||||
this.updatedBy,
|
||||
required this.nextStatus,
|
||||
required this.updateLogs,
|
||||
required this.attachments,
|
||||
required this.isActive,
|
||||
required this.isExpenseCreated,
|
||||
this.nextStatus,
|
||||
this.updateLogs,
|
||||
this.attachments,
|
||||
this.isActive,
|
||||
this.isExpenseCreated,
|
||||
});
|
||||
|
||||
factory PaymentRequestData.fromJson(Map<String, dynamic> json) =>
|
||||
PaymentRequestData(
|
||||
id: json['id'],
|
||||
title: json['title'],
|
||||
description: json['description'],
|
||||
paymentRequestUID: json['paymentRequestUID'],
|
||||
payee: json['payee'],
|
||||
currency: Currency.fromJson(json['currency']),
|
||||
amount: (json['amount'] as num).toDouble(),
|
||||
baseAmount: json['baseAmount'] != null
|
||||
? (json['baseAmount'] as num).toDouble()
|
||||
id: json['id'] as String?,
|
||||
title: json['title'] as String?,
|
||||
description: json['description'] as String?,
|
||||
paymentRequestUID: json['paymentRequestUID'] as String?,
|
||||
payee: json['payee'] as String?,
|
||||
currency: json['currency'] != null
|
||||
? Currency.fromJson(json['currency'])
|
||||
: null,
|
||||
taxAmount: json['taxAmount'] != null
|
||||
? (json['taxAmount'] as num).toDouble()
|
||||
amount: (json['amount'] as num?)?.toDouble(),
|
||||
baseAmount: (json['baseAmount'] as num?)?.toDouble(),
|
||||
taxAmount: (json['taxAmount'] as num?)?.toDouble(),
|
||||
dueDate: json['dueDate'] != null
|
||||
? DateTime.tryParse(json['dueDate'])
|
||||
: null,
|
||||
project: json['project'] != null
|
||||
? Project.fromJson(json['project'])
|
||||
: null,
|
||||
dueDate: DateTime.parse(json['dueDate']),
|
||||
project: Project.fromJson(json['project']),
|
||||
recurringPayment: json['recurringPayment'] != null
|
||||
? RecurringPayment.fromJson(json['recurringPayment'])
|
||||
: null,
|
||||
expenseCategory: ExpenseCategory.fromJson(json['expenseCategory']),
|
||||
expenseStatus: ExpenseStatus.fromJson(json['expenseStatus']),
|
||||
paidTransactionId: json['paidTransactionId'],
|
||||
paidAt: json['paidAt'] != null ? DateTime.parse(json['paidAt']) : null,
|
||||
paidBy: json['paidBy'] != null ? User.fromJson(json['paidBy']) : null,
|
||||
isAdvancePayment: json['isAdvancePayment'] ?? false,
|
||||
createdAt: DateTime.parse(json['createdAt']),
|
||||
createdBy: User.fromJson(json['createdBy']),
|
||||
updatedAt: DateTime.parse(json['updatedAt']),
|
||||
expenseCategory: json['expenseCategory'] != null
|
||||
? ExpenseCategory.fromJson(json['expenseCategory'])
|
||||
: null,
|
||||
expenseStatus: json['expenseStatus'] != null
|
||||
? ExpenseStatus.fromJson(json['expenseStatus'])
|
||||
: null,
|
||||
paidTransactionId: json['paidTransactionId'] as String?,
|
||||
paidAt: json['paidAt'] != null
|
||||
? DateTime.tryParse(json['paidAt'])
|
||||
: null,
|
||||
paidBy:
|
||||
json['paidBy'] != null ? User.fromJson(json['paidBy']) : null,
|
||||
isAdvancePayment: json['isAdvancePayment'] as bool?,
|
||||
createdAt: json['createdAt'] != null
|
||||
? DateTime.tryParse(json['createdAt'])
|
||||
: null,
|
||||
createdBy: json['createdBy'] != null
|
||||
? User.fromJson(json['createdBy'])
|
||||
: null,
|
||||
updatedAt: json['updatedAt'] != null
|
||||
? DateTime.tryParse(json['updatedAt'])
|
||||
: null,
|
||||
updatedBy:
|
||||
json['updatedBy'] != null ? User.fromJson(json['updatedBy']) : null,
|
||||
nextStatus: (json['nextStatus'] != null
|
||||
? (json['nextStatus'] as List<dynamic>)
|
||||
.map((e) => NextStatus.fromJson(e))
|
||||
.toList()
|
||||
: <NextStatus>[]),
|
||||
updateLogs: (json['updateLogs'] != null
|
||||
? (json['updateLogs'] as List<dynamic>)
|
||||
.map((e) => UpdateLog.fromJson(e))
|
||||
.toList()
|
||||
: <UpdateLog>[]),
|
||||
attachments: (json['attachments'] != null
|
||||
? (json['attachments'] as List<dynamic>)
|
||||
.map((e) => Attachment.fromJson(e))
|
||||
.toList()
|
||||
: <Attachment>[]),
|
||||
isActive: json['isActive'] ?? true,
|
||||
isExpenseCreated: json['isExpenseCreated'] ?? false,
|
||||
nextStatus: (json['nextStatus'] as List?)
|
||||
?.map((e) => NextStatus.fromJson(e))
|
||||
.toList() ??
|
||||
[],
|
||||
updateLogs: (json['updateLogs'] as List?)
|
||||
?.map((e) => UpdateLog.fromJson(e))
|
||||
.toList() ??
|
||||
[],
|
||||
attachments: (json['attachments'] as List?)
|
||||
?.map((e) => Attachment.fromJson(e))
|
||||
.toList() ??
|
||||
[],
|
||||
isActive: json['isActive'] as bool?,
|
||||
isExpenseCreated: json['isExpenseCreated'] as bool?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
@ -152,50 +166,50 @@ class PaymentRequestData {
|
||||
'description': description,
|
||||
'paymentRequestUID': paymentRequestUID,
|
||||
'payee': payee,
|
||||
'currency': currency.toJson(),
|
||||
'currency': currency?.toJson(),
|
||||
'amount': amount,
|
||||
'baseAmount': baseAmount,
|
||||
'taxAmount': taxAmount,
|
||||
'dueDate': dueDate.toIso8601String(),
|
||||
'project': project.toJson(),
|
||||
'recurringPayment': recurringPayment,
|
||||
'expenseCategory': expenseCategory.toJson(),
|
||||
'expenseStatus': expenseStatus.toJson(),
|
||||
'dueDate': dueDate?.toIso8601String(),
|
||||
'project': project?.toJson(),
|
||||
'recurringPayment': recurringPayment?.toJson(),
|
||||
'expenseCategory': expenseCategory?.toJson(),
|
||||
'expenseStatus': expenseStatus?.toJson(),
|
||||
'paidTransactionId': paidTransactionId,
|
||||
'paidAt': paidAt?.toIso8601String(),
|
||||
'paidBy': paidBy?.toJson(),
|
||||
'isAdvancePayment': isAdvancePayment,
|
||||
'createdAt': createdAt.toIso8601String(),
|
||||
'createdBy': createdBy.toJson(),
|
||||
'updatedAt': updatedAt.toIso8601String(),
|
||||
'createdAt': createdAt?.toIso8601String(),
|
||||
'createdBy': createdBy?.toJson(),
|
||||
'updatedAt': updatedAt?.toIso8601String(),
|
||||
'updatedBy': updatedBy?.toJson(),
|
||||
'nextStatus': nextStatus.map((e) => e.toJson()).toList(),
|
||||
'updateLogs': updateLogs.map((e) => e.toJson()).toList(),
|
||||
'attachments': attachments.map((e) => e.toJson()).toList(),
|
||||
'nextStatus': nextStatus?.map((e) => e.toJson()).toList(),
|
||||
'updateLogs': updateLogs?.map((e) => e.toJson()).toList(),
|
||||
'attachments': attachments?.map((e) => e.toJson()).toList(),
|
||||
'isActive': isActive,
|
||||
'isExpenseCreated': isExpenseCreated,
|
||||
};
|
||||
}
|
||||
|
||||
class RecurringPayment {
|
||||
String id;
|
||||
String recurringPaymentUID;
|
||||
double amount;
|
||||
bool isVariable;
|
||||
final String? id;
|
||||
final String? recurringPaymentUID;
|
||||
final double? amount;
|
||||
final bool? isVariable;
|
||||
|
||||
RecurringPayment({
|
||||
required this.id,
|
||||
required this.recurringPaymentUID,
|
||||
required this.amount,
|
||||
required this.isVariable,
|
||||
this.id,
|
||||
this.recurringPaymentUID,
|
||||
this.amount,
|
||||
this.isVariable,
|
||||
});
|
||||
|
||||
factory RecurringPayment.fromJson(Map<String, dynamic> json) =>
|
||||
RecurringPayment(
|
||||
id: json['id'],
|
||||
recurringPaymentUID: json['recurringPaymentUID'],
|
||||
amount: (json['amount'] as num).toDouble(),
|
||||
isVariable: json['isVariable'],
|
||||
id: json['id'] as String?,
|
||||
recurringPaymentUID: json['recurringPaymentUID'] as String?,
|
||||
amount: (json['amount'] as num?)?.toDouble(),
|
||||
isVariable: json['isVariable'] as bool?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
@ -207,26 +221,26 @@ class RecurringPayment {
|
||||
}
|
||||
|
||||
class Currency {
|
||||
String id;
|
||||
String currencyCode;
|
||||
String currencyName;
|
||||
String symbol;
|
||||
bool isActive;
|
||||
final String? id;
|
||||
final String? currencyCode;
|
||||
final String? currencyName;
|
||||
final String? symbol;
|
||||
final bool? isActive;
|
||||
|
||||
Currency({
|
||||
required this.id,
|
||||
required this.currencyCode,
|
||||
required this.currencyName,
|
||||
required this.symbol,
|
||||
required this.isActive,
|
||||
this.id,
|
||||
this.currencyCode,
|
||||
this.currencyName,
|
||||
this.symbol,
|
||||
this.isActive,
|
||||
});
|
||||
|
||||
factory Currency.fromJson(Map<String, dynamic> json) => Currency(
|
||||
id: json['id'],
|
||||
currencyCode: json['currencyCode'],
|
||||
currencyName: json['currencyName'],
|
||||
symbol: json['symbol'],
|
||||
isActive: json['isActive'],
|
||||
id: json['id'] as String?,
|
||||
currencyCode: json['currencyCode'] as String?,
|
||||
currencyName: json['currencyName'] as String?,
|
||||
symbol: json['symbol'] as String?,
|
||||
isActive: json['isActive'] as bool?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
@ -239,39 +253,39 @@ class Currency {
|
||||
}
|
||||
|
||||
class Project {
|
||||
String id;
|
||||
String name;
|
||||
final String? id;
|
||||
final String? name;
|
||||
|
||||
Project({required this.id, required this.name});
|
||||
Project({this.id, this.name});
|
||||
|
||||
factory Project.fromJson(Map<String, dynamic> json) =>
|
||||
Project(id: json['id'], name: json['name']);
|
||||
Project(id: json['id'] as String?, name: json['name'] as String?);
|
||||
|
||||
Map<String, dynamic> toJson() => {'id': id, 'name': name};
|
||||
}
|
||||
|
||||
class ExpenseCategory {
|
||||
String id;
|
||||
String name;
|
||||
bool noOfPersonsRequired;
|
||||
bool isAttachmentRequried;
|
||||
String description;
|
||||
final String? id;
|
||||
final String? name;
|
||||
final bool? noOfPersonsRequired;
|
||||
final bool? isAttachmentRequried;
|
||||
final String? description;
|
||||
|
||||
ExpenseCategory({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.noOfPersonsRequired,
|
||||
required this.isAttachmentRequried,
|
||||
required this.description,
|
||||
this.id,
|
||||
this.name,
|
||||
this.noOfPersonsRequired,
|
||||
this.isAttachmentRequried,
|
||||
this.description,
|
||||
});
|
||||
|
||||
factory ExpenseCategory.fromJson(Map<String, dynamic> json) =>
|
||||
ExpenseCategory(
|
||||
id: json['id'],
|
||||
name: json['name'],
|
||||
noOfPersonsRequired: json['noOfPersonsRequired'],
|
||||
isAttachmentRequried: json['isAttachmentRequried'],
|
||||
description: json['description'],
|
||||
id: json['id'] as String?,
|
||||
name: json['name'] as String?,
|
||||
noOfPersonsRequired: json['noOfPersonsRequired'] as bool?,
|
||||
isAttachmentRequried: json['isAttachmentRequried'] as bool?,
|
||||
description: json['description'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
@ -284,34 +298,34 @@ class ExpenseCategory {
|
||||
}
|
||||
|
||||
class ExpenseStatus {
|
||||
String id;
|
||||
String name;
|
||||
String displayName;
|
||||
String description;
|
||||
List<String>? permissionIds;
|
||||
String color;
|
||||
bool isSystem;
|
||||
final String? id;
|
||||
final String? name;
|
||||
final String? displayName;
|
||||
final String? description;
|
||||
final List<String>? permissionIds;
|
||||
final String? color;
|
||||
final bool? isSystem;
|
||||
|
||||
ExpenseStatus({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.displayName,
|
||||
required this.description,
|
||||
this.id,
|
||||
this.name,
|
||||
this.displayName,
|
||||
this.description,
|
||||
this.permissionIds,
|
||||
required this.color,
|
||||
required this.isSystem,
|
||||
this.color,
|
||||
this.isSystem,
|
||||
});
|
||||
|
||||
factory ExpenseStatus.fromJson(Map<String, dynamic> json) => ExpenseStatus(
|
||||
id: json['id'],
|
||||
name: json['name'],
|
||||
displayName: json['displayName'],
|
||||
description: json['description'],
|
||||
id: json['id'] as String?,
|
||||
name: json['name'] as String?,
|
||||
displayName: json['displayName'] as String?,
|
||||
description: json['description'] as String?,
|
||||
permissionIds: json['permissionIds'] != null
|
||||
? List<String>.from(json['permissionIds'])
|
||||
: null,
|
||||
color: json['color'],
|
||||
isSystem: json['isSystem'],
|
||||
color: json['color'] as String?,
|
||||
isSystem: json['isSystem'] as bool?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
@ -326,32 +340,32 @@ class ExpenseStatus {
|
||||
}
|
||||
|
||||
class User {
|
||||
String id;
|
||||
String firstName;
|
||||
String lastName;
|
||||
String email;
|
||||
String photo;
|
||||
String jobRoleId;
|
||||
String jobRoleName;
|
||||
final String? id;
|
||||
final String? firstName;
|
||||
final String? lastName;
|
||||
final String? email;
|
||||
final String? photo;
|
||||
final String? jobRoleId;
|
||||
final String? jobRoleName;
|
||||
|
||||
User({
|
||||
required this.id,
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
required this.email,
|
||||
required this.photo,
|
||||
required this.jobRoleId,
|
||||
required this.jobRoleName,
|
||||
this.id,
|
||||
this.firstName,
|
||||
this.lastName,
|
||||
this.email,
|
||||
this.photo,
|
||||
this.jobRoleId,
|
||||
this.jobRoleName,
|
||||
});
|
||||
|
||||
factory User.fromJson(Map<String, dynamic> json) => User(
|
||||
id: json['id'],
|
||||
firstName: json['firstName'],
|
||||
lastName: json['lastName'],
|
||||
email: json['email'],
|
||||
photo: json['photo'],
|
||||
jobRoleId: json['jobRoleId'],
|
||||
jobRoleName: json['jobRoleName'],
|
||||
id: json['id'] as String?,
|
||||
firstName: json['firstName'] as String?,
|
||||
lastName: json['lastName'] as String?,
|
||||
email: json['email'] as String?,
|
||||
photo: json['photo'] as String?,
|
||||
jobRoleId: json['jobRoleId'] as String?,
|
||||
jobRoleName: json['jobRoleName'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
@ -366,34 +380,34 @@ class User {
|
||||
}
|
||||
|
||||
class NextStatus {
|
||||
String id;
|
||||
String name;
|
||||
String displayName;
|
||||
String description;
|
||||
List<String>? permissionIds;
|
||||
String color;
|
||||
bool isSystem;
|
||||
final String? id;
|
||||
final String? name;
|
||||
final String? displayName;
|
||||
final String? description;
|
||||
final List<String>? permissionIds;
|
||||
final String? color;
|
||||
final bool? isSystem;
|
||||
|
||||
NextStatus({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.displayName,
|
||||
required this.description,
|
||||
this.id,
|
||||
this.name,
|
||||
this.displayName,
|
||||
this.description,
|
||||
this.permissionIds,
|
||||
required this.color,
|
||||
required this.isSystem,
|
||||
this.color,
|
||||
this.isSystem,
|
||||
});
|
||||
|
||||
factory NextStatus.fromJson(Map<String, dynamic> json) => NextStatus(
|
||||
id: json['id'],
|
||||
name: json['name'],
|
||||
displayName: json['displayName'],
|
||||
description: json['description'],
|
||||
id: json['id'] as String?,
|
||||
name: json['name'] as String?,
|
||||
displayName: json['displayName'] as String?,
|
||||
description: json['description'] as String?,
|
||||
permissionIds: json['permissionIds'] != null
|
||||
? List<String>.from(json['permissionIds'])
|
||||
: null,
|
||||
color: json['color'],
|
||||
isSystem: json['isSystem'],
|
||||
color: json['color'] as String?,
|
||||
isSystem: json['isSystem'] as bool?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
@ -408,67 +422,73 @@ class NextStatus {
|
||||
}
|
||||
|
||||
class UpdateLog {
|
||||
String id;
|
||||
ExpenseStatus? status;
|
||||
ExpenseStatus nextStatus;
|
||||
String comment;
|
||||
DateTime updatedAt;
|
||||
User updatedBy;
|
||||
final String? id;
|
||||
final ExpenseStatus? status;
|
||||
final ExpenseStatus? nextStatus;
|
||||
final String? comment;
|
||||
final DateTime? updatedAt;
|
||||
final User? updatedBy;
|
||||
|
||||
UpdateLog({
|
||||
required this.id,
|
||||
this.id,
|
||||
this.status,
|
||||
required this.nextStatus,
|
||||
required this.comment,
|
||||
required this.updatedAt,
|
||||
required this.updatedBy,
|
||||
this.nextStatus,
|
||||
this.comment,
|
||||
this.updatedAt,
|
||||
this.updatedBy,
|
||||
});
|
||||
|
||||
factory UpdateLog.fromJson(Map<String, dynamic> json) => UpdateLog(
|
||||
id: json['id'],
|
||||
id: json['id'] as String?,
|
||||
status: json['status'] != null
|
||||
? ExpenseStatus.fromJson(json['status'])
|
||||
: null,
|
||||
nextStatus: ExpenseStatus.fromJson(json['nextStatus']),
|
||||
comment: json['comment'],
|
||||
updatedAt: DateTime.parse(json['updatedAt']),
|
||||
updatedBy: User.fromJson(json['updatedBy']),
|
||||
nextStatus: json['nextStatus'] != null
|
||||
? ExpenseStatus.fromJson(json['nextStatus'])
|
||||
: null,
|
||||
comment: json['comment'] as String?,
|
||||
updatedAt: json['updatedAt'] != null
|
||||
? DateTime.tryParse(json['updatedAt'])
|
||||
: null,
|
||||
updatedBy: json['updatedBy'] != null
|
||||
? User.fromJson(json['updatedBy'])
|
||||
: null,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'status': status?.toJson(),
|
||||
'nextStatus': nextStatus.toJson(),
|
||||
'nextStatus': nextStatus?.toJson(),
|
||||
'comment': comment,
|
||||
'updatedAt': updatedAt.toIso8601String(),
|
||||
'updatedBy': updatedBy.toJson(),
|
||||
'updatedAt': updatedAt?.toIso8601String(),
|
||||
'updatedBy': updatedBy?.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
class Attachment {
|
||||
String id;
|
||||
String fileName;
|
||||
String url;
|
||||
String? thumbUrl;
|
||||
int fileSize;
|
||||
String contentType;
|
||||
final String? id;
|
||||
final String? fileName;
|
||||
final String? url;
|
||||
final String? thumbUrl;
|
||||
final int? fileSize;
|
||||
final String? contentType;
|
||||
|
||||
Attachment({
|
||||
required this.id,
|
||||
required this.fileName,
|
||||
required this.url,
|
||||
this.id,
|
||||
this.fileName,
|
||||
this.url,
|
||||
this.thumbUrl,
|
||||
required this.fileSize,
|
||||
required this.contentType,
|
||||
this.fileSize,
|
||||
this.contentType,
|
||||
});
|
||||
|
||||
factory Attachment.fromJson(Map<String, dynamic> json) => Attachment(
|
||||
id: json['id'],
|
||||
fileName: json['fileName'],
|
||||
url: json['url'],
|
||||
thumbUrl: json['thumbUrl'],
|
||||
fileSize: json['fileSize'],
|
||||
contentType: json['contentType'],
|
||||
id: json['id'] as String?,
|
||||
fileName: json['fileName'] as String?,
|
||||
url: json['url'] as String?,
|
||||
thumbUrl: json['thumbUrl'] as String?,
|
||||
fileSize: json['fileSize'] as int?,
|
||||
contentType: json['contentType'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
|
||||
@ -50,9 +50,9 @@ class _PaymentRequestDetailScreenState extends State<PaymentRequestDetailScreen>
|
||||
|
||||
void _checkPermissionToSubmit(PaymentRequestData request) {
|
||||
const draftStatusId = '6537018f-f4e9-4cb3-a210-6c3b2da999d7';
|
||||
final isCreatedByCurrentUser = employeeInfo?.id == request.createdBy.id;
|
||||
final isCreatedByCurrentUser = employeeInfo?.id == request.createdBy?.id;
|
||||
final hasDraftNextStatus =
|
||||
request.nextStatus.any((s) => s.id == draftStatusId);
|
||||
(request.nextStatus ?? []).any((s) => (s.id ?? '') == draftStatusId);
|
||||
canSubmit.value = isCreatedByCurrentUser && hasDraftNextStatus;
|
||||
}
|
||||
|
||||
@ -61,36 +61,41 @@ class _PaymentRequestDetailScreenState extends State<PaymentRequestDetailScreen>
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
Color _parseColor(String hexColor) {
|
||||
String hex = hexColor.toUpperCase().replaceAll('#', '');
|
||||
if (hex.length == 6) hex = 'FF$hex';
|
||||
return Color(int.parse(hex, radix: 16));
|
||||
Color _parseColor(String? hexColor) {
|
||||
try {
|
||||
if (hexColor == null || hexColor.trim().isEmpty) return Colors.grey;
|
||||
String hex = hexColor.toUpperCase().replaceAll('#', '');
|
||||
if (hex.length == 6) hex = 'FF$hex';
|
||||
return Color(int.parse(hex, radix: 16));
|
||||
} catch (_) {
|
||||
return Colors.grey;
|
||||
}
|
||||
}
|
||||
|
||||
void _openEditPaymentRequestBottomSheet(request) {
|
||||
void _openEditPaymentRequestBottomSheet(PaymentRequestData request) {
|
||||
showPaymentRequestBottomSheet(
|
||||
isEdit: true,
|
||||
existingData: {
|
||||
"id": request.id,
|
||||
"paymentRequestId": request.paymentRequestUID,
|
||||
"title": request.title,
|
||||
"projectId": request.project.id,
|
||||
"projectName": request.project.name,
|
||||
"expenseCategoryId": request.expenseCategory.id,
|
||||
"expenseCategoryName": request.expenseCategory.name,
|
||||
"amount": request.amount.toString(),
|
||||
"currencyId": request.currency.id,
|
||||
"currencySymbol": request.currency.symbol,
|
||||
"payee": request.payee,
|
||||
"description": request.description,
|
||||
"isAdvancePayment": request.isAdvancePayment,
|
||||
"dueDate": request.dueDate,
|
||||
"attachments": request.attachments
|
||||
"id": request.id ?? '',
|
||||
"paymentRequestId": request.paymentRequestUID ?? '',
|
||||
"title": request.title ?? '',
|
||||
"projectId": request.project?.id ?? '',
|
||||
"projectName": request.project?.name ?? '',
|
||||
"expenseCategoryId": request.expenseCategory?.id ?? '',
|
||||
"expenseCategoryName": request.expenseCategory?.name ?? '',
|
||||
"amount": (request.amount ?? 0).toString(),
|
||||
"currencyId": request.currency?.id ?? '',
|
||||
"currencySymbol": request.currency?.symbol ?? '',
|
||||
"payee": request.payee ?? '',
|
||||
"description": request.description ?? '',
|
||||
"isAdvancePayment": request.isAdvancePayment ?? false,
|
||||
"dueDate": request.dueDate?.toIso8601String() ?? '',
|
||||
"attachments": (request.attachments ?? [])
|
||||
.map((a) => {
|
||||
"url": a.url,
|
||||
"fileName": a.fileName,
|
||||
"documentId": a.id,
|
||||
"contentType": a.contentType,
|
||||
"url": a.url ?? '',
|
||||
"fileName": a.fileName ?? '',
|
||||
"documentId": a.id ?? '',
|
||||
"contentType": a.contentType ?? '',
|
||||
})
|
||||
.toList(),
|
||||
},
|
||||
@ -105,70 +110,72 @@ class _PaymentRequestDetailScreenState extends State<PaymentRequestDetailScreen>
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: _buildAppBar(),
|
||||
body: SafeArea(child: Obx(() {
|
||||
if (controller.isLoading.value &&
|
||||
controller.paymentRequest.value == null) {
|
||||
return SkeletonLoaders.paymentRequestDetailSkeletonLoader();
|
||||
}
|
||||
body: SafeArea(
|
||||
child: Obx(() {
|
||||
if (controller.isLoading.value &&
|
||||
controller.paymentRequest.value == null) {
|
||||
return SkeletonLoaders.paymentRequestDetailSkeletonLoader();
|
||||
}
|
||||
|
||||
final request = controller.paymentRequest.value;
|
||||
final request = controller.paymentRequest.value;
|
||||
|
||||
if (controller.errorMessage.isNotEmpty) {
|
||||
return Center(
|
||||
child: MyText.bodyMedium(controller.errorMessage.value));
|
||||
}
|
||||
if ((controller.errorMessage.value ).isNotEmpty) {
|
||||
return Center(child: MyText.bodyMedium(controller.errorMessage.value));
|
||||
}
|
||||
|
||||
if (request == null) {
|
||||
return Center(child: MyText.bodyMedium("No data to display."));
|
||||
}
|
||||
if (request == null) {
|
||||
return Center(child: MyText.bodyMedium("No data to display."));
|
||||
}
|
||||
|
||||
return MyRefreshIndicator(
|
||||
onRefresh: controller.fetchPaymentRequestDetail,
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
12,
|
||||
12,
|
||||
12,
|
||||
60 + MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
child: Center(
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 520),
|
||||
child: Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(5)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12, horizontal: 14),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_Header(
|
||||
request: request,
|
||||
colorParser: _parseColor,
|
||||
employeeInfo: employeeInfo,
|
||||
onEdit: () =>
|
||||
_openEditPaymentRequestBottomSheet(request),
|
||||
),
|
||||
const Divider(height: 30, thickness: 1.2),
|
||||
_Logs(
|
||||
logs: request.updateLogs, colorParser: _parseColor),
|
||||
const Divider(height: 30, thickness: 1.2),
|
||||
_Parties(request: request),
|
||||
const Divider(height: 30, thickness: 1.2),
|
||||
_DetailsTable(request: request),
|
||||
const Divider(height: 30, thickness: 1.2),
|
||||
_Documents(documents: request.attachments),
|
||||
MySpacing.height(24),
|
||||
],
|
||||
return MyRefreshIndicator(
|
||||
onRefresh: controller.fetchPaymentRequestDetail,
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
12,
|
||||
12,
|
||||
12,
|
||||
60 + MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
child: Center(
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 520),
|
||||
child: Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(5)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12, horizontal: 14),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_Header(
|
||||
request: request,
|
||||
colorParser: _parseColor,
|
||||
employeeInfo: employeeInfo,
|
||||
onEdit: () => _openEditPaymentRequestBottomSheet(request),
|
||||
),
|
||||
const Divider(height: 30, thickness: 1.2),
|
||||
_Logs(
|
||||
logs: request.updateLogs ?? [],
|
||||
colorParser: _parseColor,
|
||||
),
|
||||
const Divider(height: 30, thickness: 1.2),
|
||||
_Parties(request: request),
|
||||
const Divider(height: 30, thickness: 1.2),
|
||||
_DetailsTable(request: request),
|
||||
const Divider(height: 30, thickness: 1.2),
|
||||
_Documents(documents: request.attachments ?? []),
|
||||
MySpacing.height(24),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
})),
|
||||
);
|
||||
}),
|
||||
),
|
||||
bottomNavigationBar: _buildBottomActionBar(),
|
||||
);
|
||||
}
|
||||
@ -192,14 +199,18 @@ class _PaymentRequestDetailScreenState extends State<PaymentRequestDetailScreen>
|
||||
const reimbursementStatusId = '61578360-3a49-4c34-8604-7b35a3787b95';
|
||||
const draftStatusId = '6537018f-f4e9-4cb3-a210-6c3b2da999d7';
|
||||
|
||||
final availableStatuses = request.nextStatus.where((status) {
|
||||
if (status.id == draftStatusId) {
|
||||
return employeeInfo?.id == request.createdBy.id;
|
||||
final availableStatuses = (request.nextStatus ?? []).where((status) {
|
||||
if ((status.id ?? '') == draftStatusId) {
|
||||
return employeeInfo?.id == request.createdBy?.id;
|
||||
}
|
||||
return permissionController
|
||||
.hasAnyPermission(status.permissionIds ?? []);
|
||||
}).toList();
|
||||
|
||||
if (availableStatuses.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return SafeArea(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
@ -224,7 +235,10 @@ class _PaymentRequestDetailScreenState extends State<PaymentRequestDetailScreen>
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
if (status.id == reimbursementStatusId) {
|
||||
final statusId = status.id ?? '';
|
||||
final dispName = status.displayName ?? '';
|
||||
|
||||
if (statusId == reimbursementStatusId) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
@ -233,23 +247,23 @@ class _PaymentRequestDetailScreenState extends State<PaymentRequestDetailScreen>
|
||||
BorderRadius.vertical(top: Radius.circular(5)),
|
||||
),
|
||||
builder: (ctx) => UpdatePaymentRequestWithReimbursement(
|
||||
expenseId: request.paymentRequestUID,
|
||||
statusId: status.id,
|
||||
expenseId: request.paymentRequestUID ?? '',
|
||||
statusId: statusId,
|
||||
onClose: () {},
|
||||
),
|
||||
);
|
||||
} else if (status.id ==
|
||||
} else if (statusId ==
|
||||
'b8586f67-dc19-49c3-b4af-224149efe1d3') {
|
||||
showCreateExpenseBottomSheet(
|
||||
statusId: status.id,
|
||||
statusId: statusId,
|
||||
);
|
||||
} else {
|
||||
final comment = await showCommentBottomSheet(
|
||||
context, status.displayName);
|
||||
final comment =
|
||||
await showCommentBottomSheet(context, dispName);
|
||||
if (comment == null || comment.trim().isEmpty) return;
|
||||
|
||||
final success = await controller.updatePaymentRequestStatus(
|
||||
statusId: status.id,
|
||||
statusId: statusId,
|
||||
comment: comment.trim(),
|
||||
);
|
||||
|
||||
@ -258,14 +272,15 @@ class _PaymentRequestDetailScreenState extends State<PaymentRequestDetailScreen>
|
||||
message: success
|
||||
? 'Status updated successfully'
|
||||
: 'Failed to update status',
|
||||
type: success ? SnackbarType.success : SnackbarType.error,
|
||||
type:
|
||||
success ? SnackbarType.success : SnackbarType.error,
|
||||
);
|
||||
|
||||
if (success) await controller.fetchPaymentRequestDetail();
|
||||
}
|
||||
},
|
||||
child: MyText.bodySmall(
|
||||
status.displayName,
|
||||
status.displayName ?? '',
|
||||
color: Colors.white,
|
||||
fontWeight: 600,
|
||||
),
|
||||
@ -308,8 +323,8 @@ class _PaymentRequestDetailScreenState extends State<PaymentRequestDetailScreen>
|
||||
),
|
||||
MySpacing.height(2),
|
||||
GetBuilder<ProjectController>(builder: (_) {
|
||||
final name = projectController.selectedProject?.name ??
|
||||
'Select Project';
|
||||
final name =
|
||||
projectController.selectedProject?.name ?? 'Select Project';
|
||||
return Row(
|
||||
children: [
|
||||
const Icon(Icons.work_outline,
|
||||
@ -340,14 +355,14 @@ class _PaymentRequestDetailScreenState extends State<PaymentRequestDetailScreen>
|
||||
class PaymentRequestPermissionHelper {
|
||||
static bool canEditPaymentRequest(
|
||||
EmployeeInfo? employee, PaymentRequestData request) {
|
||||
return employee?.id == request.createdBy.id &&
|
||||
_isInAllowedEditStatus(request.expenseStatus.id);
|
||||
return employee?.id == request.createdBy?.id &&
|
||||
_isInAllowedEditStatus(request.expenseStatus?.id ?? '');
|
||||
}
|
||||
|
||||
static bool canSubmitPaymentRequest(
|
||||
EmployeeInfo? employee, PaymentRequestData request) {
|
||||
return employee?.id == request.createdBy.id &&
|
||||
request.nextStatus.isNotEmpty;
|
||||
return employee?.id == request.createdBy?.id &&
|
||||
(request.nextStatus ?? []).isNotEmpty;
|
||||
}
|
||||
|
||||
static bool _isInAllowedEditStatus(String statusId) {
|
||||
@ -362,9 +377,10 @@ class PaymentRequestPermissionHelper {
|
||||
|
||||
// ------------------ Sub-widgets ------------------
|
||||
|
||||
// Header widget
|
||||
class _Header extends StatefulWidget {
|
||||
final PaymentRequestData request;
|
||||
final Color Function(String) colorParser;
|
||||
final Color Function(String?) colorParser;
|
||||
final VoidCallback? onEdit;
|
||||
final EmployeeInfo? employeeInfo;
|
||||
|
||||
@ -382,7 +398,7 @@ class _Header extends StatefulWidget {
|
||||
class _HeaderState extends State<_Header> with UIMixin {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final statusColor = widget.colorParser(widget.request.expenseStatus.color);
|
||||
final statusColor = widget.colorParser(widget.request.expenseStatus?.color);
|
||||
|
||||
final canEdit = widget.employeeInfo != null &&
|
||||
PaymentRequestPermissionHelper.canEditPaymentRequest(
|
||||
@ -395,7 +411,7 @@ class _HeaderState extends State<_Header> with UIMixin {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
MyText.bodyMedium(
|
||||
'ID: ${widget.request.paymentRequestUID}',
|
||||
'ID: ${widget.request.paymentRequestUID ?? '-'}',
|
||||
fontWeight: 700,
|
||||
fontSize: 14,
|
||||
),
|
||||
@ -422,7 +438,7 @@ class _HeaderState extends State<_Header> with UIMixin {
|
||||
Expanded(
|
||||
child: MyText.bodySmall(
|
||||
DateTimeUtils.convertUtcToLocal(
|
||||
widget.request.createdAt.toIso8601String(),
|
||||
widget.request.createdAt?.toIso8601String() ?? '',
|
||||
format: 'dd MMM yyyy'),
|
||||
fontWeight: 600,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
@ -443,7 +459,7 @@ class _HeaderState extends State<_Header> with UIMixin {
|
||||
SizedBox(
|
||||
width: 100,
|
||||
child: MyText.labelSmall(
|
||||
widget.request.expenseStatus.displayName,
|
||||
widget.request.expenseStatus?.displayName ?? '-',
|
||||
color: statusColor,
|
||||
fontWeight: 600,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
@ -459,11 +475,10 @@ class _HeaderState extends State<_Header> with UIMixin {
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------ Logs, Parties, Details, Documents ------------------
|
||||
|
||||
// Logs widget
|
||||
class _Logs extends StatelessWidget {
|
||||
final List<UpdateLog> logs;
|
||||
final Color Function(String) colorParser;
|
||||
final Color Function(String?) colorParser;
|
||||
const _Logs({required this.logs, required this.colorParser});
|
||||
|
||||
@override
|
||||
@ -490,21 +505,25 @@ class _Logs extends StatelessWidget {
|
||||
? colorParser(log.status!.color)
|
||||
: Colors.grey;
|
||||
|
||||
final comment = log.comment;
|
||||
final nextStatusName = log.nextStatus.name;
|
||||
final comment = log.comment ?? '';
|
||||
final nextStatusName = log.nextStatus?.name ?? '';
|
||||
|
||||
final updatedBy = log.updatedBy;
|
||||
final first = updatedBy?.firstName ?? '';
|
||||
final last = updatedBy?.lastName ?? '';
|
||||
final initials =
|
||||
'${updatedBy.firstName.isNotEmpty == true ? updatedBy.firstName[0] : ''}'
|
||||
'${updatedBy.lastName.isNotEmpty == true ? updatedBy.lastName[0] : ''}';
|
||||
final name = '${updatedBy.firstName} ${updatedBy.lastName}';
|
||||
'${first.isNotEmpty ? first[0] : ''}${last.isNotEmpty ? last[0] : ''}';
|
||||
final name = ((first + ' ' + last).trim().isNotEmpty)
|
||||
? '$first $last'
|
||||
: '-';
|
||||
|
||||
final timestamp = log.updatedAt
|
||||
.toUtc()
|
||||
.add(const Duration(hours: 5, minutes: 30));
|
||||
final timeAgo = timeago.format(timestamp);
|
||||
final updatedAt = log.updatedAt;
|
||||
final timeAgo = (updatedAt != null)
|
||||
? timeago.format(updatedAt.toUtc().add(const Duration(hours: 5, minutes: 30)))
|
||||
: '-';
|
||||
|
||||
final nextStatusColor = colorParser(log.nextStatus.color);
|
||||
final nextStatusColor =
|
||||
colorParser(log.nextStatus?.color ?? '');
|
||||
|
||||
return TimelineTile(
|
||||
alignment: TimelineAlign.start,
|
||||
@ -583,6 +602,7 @@ class _Logs extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// Parties widget
|
||||
class _Parties extends StatelessWidget {
|
||||
final PaymentRequestData request;
|
||||
const _Parties({required this.request});
|
||||
@ -602,7 +622,7 @@ class _Parties extends StatelessWidget {
|
||||
children: [
|
||||
MyText.labelMedium("Payee", fontWeight: 600),
|
||||
MySpacing.height(2),
|
||||
MyText.bodyMedium(request.payee),
|
||||
MyText.bodyMedium(request.payee ?? '-'),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -612,7 +632,7 @@ class _Parties extends StatelessWidget {
|
||||
children: [
|
||||
MyText.labelMedium("Project", fontWeight: 600),
|
||||
MySpacing.height(2),
|
||||
MyText.bodyMedium(request.project.name),
|
||||
MyText.bodyMedium(request.project?.name ?? '-'),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -623,98 +643,105 @@ class _Parties extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// Details table widget
|
||||
class _DetailsTable extends StatelessWidget {
|
||||
final PaymentRequestData request;
|
||||
const _DetailsTable({required this.request});
|
||||
|
||||
String _formatCurrencyAmount(String? symbol, double? amount) {
|
||||
final sym = symbol ?? '';
|
||||
final amt = amount ?? 0.0;
|
||||
return '$sym ${amt.toStringAsFixed(2)}';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final currencySymbol = request.currency?.symbol ?? '';
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Basic Info
|
||||
_labelValueRow("Payment Request ID:", request.paymentRequestUID),
|
||||
if (request.paidTransactionId != null &&
|
||||
request.paidTransactionId!.isNotEmpty)
|
||||
_labelValueRow("Transaction ID:", request.paidTransactionId!),
|
||||
_labelValueRow("Payee:", request.payee),
|
||||
_labelValueRow("Project:", request.project.name),
|
||||
_labelValueRow("Expense Category:", request.expenseCategory.name),
|
||||
_labelValueRow("Payment Request ID:", request.paymentRequestUID ?? '-'),
|
||||
if ((request.paidTransactionId ?? '').isNotEmpty)
|
||||
_labelValueRow("Transaction ID:", request.paidTransactionId ?? ''),
|
||||
_labelValueRow("Payee:", request.payee ?? '-'),
|
||||
_labelValueRow("Project:", request.project?.name ?? '-'),
|
||||
_labelValueRow("Expense Category:", request.expenseCategory?.name ?? '-'),
|
||||
|
||||
// Amounts
|
||||
_labelValueRow("Amount:",
|
||||
"${request.currency.symbol} ${request.amount.toStringAsFixed(2)}"),
|
||||
_labelValueRow("Amount:", _formatCurrencyAmount(currencySymbol, request.amount)),
|
||||
if (request.baseAmount != null)
|
||||
_labelValueRow("Base Amount:",
|
||||
"${request.currency.symbol} ${request.baseAmount!.toStringAsFixed(2)}"),
|
||||
_labelValueRow("Base Amount:", _formatCurrencyAmount(currencySymbol, request.baseAmount)),
|
||||
if (request.taxAmount != null)
|
||||
_labelValueRow("Tax Amount:",
|
||||
"${request.currency.symbol} ${request.taxAmount!.toStringAsFixed(2)}"),
|
||||
if (request.expenseCategory.noOfPersonsRequired)
|
||||
_labelValueRow("Tax Amount:", _formatCurrencyAmount(currencySymbol, request.taxAmount)),
|
||||
if (request.expenseCategory?.noOfPersonsRequired == true)
|
||||
_labelValueRow("Additional Persons Required:", "Yes"),
|
||||
if (request.expenseCategory.isAttachmentRequried)
|
||||
if (request.expenseCategory?.isAttachmentRequried == true)
|
||||
_labelValueRow("Attachment Required:", "Yes"),
|
||||
|
||||
// Dates
|
||||
_labelValueRow(
|
||||
"Due Date:",
|
||||
DateTimeUtils.convertUtcToLocal(request.dueDate.toIso8601String(),
|
||||
DateTimeUtils.convertUtcToLocal(
|
||||
request.dueDate?.toIso8601String() ?? '',
|
||||
format: 'dd MMM yyyy')),
|
||||
_labelValueRow(
|
||||
"Created At:",
|
||||
DateTimeUtils.convertUtcToLocal(request.createdAt.toIso8601String(),
|
||||
DateTimeUtils.convertUtcToLocal(
|
||||
request.createdAt?.toIso8601String() ?? '',
|
||||
format: 'dd MMM yyyy')),
|
||||
_labelValueRow(
|
||||
"Updated At:",
|
||||
DateTimeUtils.convertUtcToLocal(request.updatedAt.toIso8601String(),
|
||||
DateTimeUtils.convertUtcToLocal(
|
||||
request.updatedAt?.toIso8601String() ?? '',
|
||||
format: 'dd MMM yyyy')),
|
||||
|
||||
// Payment Info
|
||||
if (request.paidAt != null)
|
||||
_labelValueRow(
|
||||
"Transaction Date:",
|
||||
DateTimeUtils.convertUtcToLocal(request.paidAt!.toIso8601String(),
|
||||
format: 'dd MMM yyyy')),
|
||||
"Transaction Date:",
|
||||
DateTimeUtils.convertUtcToLocal(
|
||||
request.paidAt?.toIso8601String() ?? '',
|
||||
format: 'dd MMM yyyy'),
|
||||
),
|
||||
if (request.paidBy != null)
|
||||
_labelValueRow("Paid By:",
|
||||
"${request.paidBy!.firstName} ${request.paidBy!.lastName}"),
|
||||
_labelValueRow("Paid By:", "${request.paidBy?.firstName ?? ''} ${request.paidBy?.lastName ?? ''}".trim()),
|
||||
|
||||
// Flags
|
||||
_labelValueRow(
|
||||
"Advance Payment:", request.isAdvancePayment ? "Yes" : "No"),
|
||||
"Advance Payment:", (request.isAdvancePayment ?? false) ? "Yes" : "No"),
|
||||
_labelValueRow(
|
||||
"Expense Created:", request.isExpenseCreated ? "Yes" : "No"),
|
||||
_labelValueRow("Active:", request.isActive ? "Yes" : "No"),
|
||||
"Expense Created:", (request.isExpenseCreated ?? false) ? "Yes" : "No"),
|
||||
_labelValueRow("Active:", (request.isActive ?? false) ? "Yes" : "No"),
|
||||
|
||||
// Recurring Payment Info
|
||||
if (request.recurringPayment != null) ...[
|
||||
const SizedBox(height: 6),
|
||||
MyText.bodySmall("Recurring Payment Info:", fontWeight: 600),
|
||||
_labelValueRow(
|
||||
"Recurring ID:", request.recurringPayment!.recurringPaymentUID),
|
||||
_labelValueRow("Amount:",
|
||||
"${request.currency.symbol} ${request.recurringPayment!.amount.toStringAsFixed(2)}"),
|
||||
_labelValueRow("Variable Amount:",
|
||||
request.recurringPayment!.isVariable ? "Yes" : "No"),
|
||||
_labelValueRow("Recurring ID:", request.recurringPayment?.recurringPaymentUID ?? '-'),
|
||||
_labelValueRow("Amount:", _formatCurrencyAmount(currencySymbol, request.recurringPayment?.amount)),
|
||||
_labelValueRow("Variable Amount:", (request.recurringPayment?.isVariable ?? false) ? "Yes" : "No"),
|
||||
],
|
||||
|
||||
// Description & Attachments
|
||||
_labelValueRow("Description:", request.description),
|
||||
_labelValueRow(
|
||||
"Attachment:", request.attachments.isNotEmpty ? "Yes" : "No"),
|
||||
_labelValueRow("Description:", request.description ?? '-'),
|
||||
_labelValueRow("Attachment:", (request.attachments ?? []).isNotEmpty ? "Yes" : "No"),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Documents widget
|
||||
class _Documents extends StatelessWidget {
|
||||
final List<Attachment> documents;
|
||||
const _Documents({required this.documents});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (documents.isEmpty)
|
||||
if (documents.isEmpty) {
|
||||
return MyText.bodyMedium('No Documents', color: Colors.grey);
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -728,26 +755,36 @@ class _Documents extends StatelessWidget {
|
||||
separatorBuilder: (_, __) => const SizedBox(height: 8),
|
||||
itemBuilder: (context, index) {
|
||||
final doc = documents[index];
|
||||
final isImage = doc.contentType.startsWith('image/');
|
||||
final contentType = doc.contentType ?? '';
|
||||
final isImage = contentType.startsWith('image/');
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
final imageDocs = documents
|
||||
.where((d) => d.contentType.startsWith('image/'))
|
||||
.where((d) => (d.contentType ?? '').startsWith('image/'))
|
||||
.toList();
|
||||
final initialIndex =
|
||||
imageDocs.indexWhere((d) => d.id == doc.id);
|
||||
imageDocs.indexWhere((d) => (d.id ?? '') == (doc.id ?? ''));
|
||||
|
||||
if (isImage && imageDocs.isNotEmpty && initialIndex != -1) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) => ImageViewerDialog(
|
||||
imageSources: imageDocs.map((e) => e.url).toList(),
|
||||
imageSources: imageDocs.map((e) => e.url ?? '').toList(),
|
||||
initialIndex: initialIndex,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
final Uri url = Uri.parse(doc.url);
|
||||
final urlStr = doc.url ?? '';
|
||||
if (urlStr.isEmpty) {
|
||||
showAppSnackbar(
|
||||
title: 'Error',
|
||||
message: 'Document URL is missing.',
|
||||
type: SnackbarType.error,
|
||||
);
|
||||
return;
|
||||
}
|
||||
final Uri url = Uri.parse(urlStr);
|
||||
if (await canLaunchUrl(url)) {
|
||||
await launchUrl(url, mode: LaunchMode.externalApplication);
|
||||
} else {
|
||||
@ -774,7 +811,7 @@ class _Documents extends StatelessWidget {
|
||||
const SizedBox(width: 7),
|
||||
Expanded(
|
||||
child: MyText.bodySmall(
|
||||
doc.fileName,
|
||||
doc.fileName ?? (doc.url ?? '-'),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user