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