백엔드 계약 문서 동기화하고 DTO 파서 정합성 확장

This commit is contained in:
JiWoong Sul
2025-10-17 00:52:30 +09:00
parent efed3c1a6f
commit 7522f46693
10 changed files with 660 additions and 194 deletions

View File

@@ -44,38 +44,83 @@ class ApprovalDto {
/// API 응답 JSON을 [ApprovalDto]로 변환한다.
factory ApprovalDto.fromJson(Map<String, dynamic> json) {
final approvalEnvelope = _mapOrEmpty(json['approval']);
final statusMap = _firstNonEmptyMap([
json['status'],
json['approval_status'],
approvalEnvelope['status'],
approvalEnvelope['approval_status'],
]);
final requesterMap = _firstNonEmptyMap([
json['requester'],
json['requested_by'],
approvalEnvelope['requester'],
approvalEnvelope['requested_by'],
]);
final currentStepMap = _firstNonEmptyMap([
json['current_step'],
json['currentStep'],
approvalEnvelope['current_step'],
]);
final transactionMap = _mapOrEmpty(json['transaction']);
final envelopeTransactionMap = _mapOrEmpty(approvalEnvelope['transaction']);
var stepsSource = _asListOfMap(json['steps']);
if (stepsSource.isEmpty) {
stepsSource = _asListOfMap(approvalEnvelope['steps']);
}
var historiesSource = _asListOfMap(json['histories']);
if (historiesSource.isEmpty) {
historiesSource = _asListOfMap(approvalEnvelope['histories']);
}
final currentStepDto = currentStepMap.isEmpty
? null
: ApprovalStepDto.fromJson(currentStepMap);
final approvalNo =
_pickString(
[json, approvalEnvelope],
const ['approval_no', 'approvalNo'],
) ??
'-';
final transactionNo = _pickString(
[json, transactionMap, approvalEnvelope, envelopeTransactionMap],
const ['transaction_no', 'transactionNo'],
);
return ApprovalDto(
id: json['id'] as int?,
approvalNo: json['approval_no'] as String,
transactionNo: json['transaction'] is Map<String, dynamic>
? (json['transaction']['transaction_no'] as String?)
: json['transaction_no'] as String?,
status: ApprovalStatusDto.fromJson(
(json['status'] as Map<String, dynamic>? ?? const {}),
id: json['id'] as int? ?? approvalEnvelope['id'] as int?,
approvalNo: approvalNo,
transactionNo: transactionNo,
status: ApprovalStatusDto.fromJson(statusMap),
currentStep: currentStepDto,
requester: ApprovalRequesterDto.fromJson(requesterMap),
requestedAt:
_parseDate(
json['requested_at'] ?? approvalEnvelope['requested_at'],
) ??
DateTime.now(),
decidedAt: _parseDate(
json['decided_at'] ?? approvalEnvelope['decided_at'],
),
currentStep: json['current_step'] is Map<String, dynamic>
? ApprovalStepDto.fromJson(
json['current_step'] as Map<String, dynamic>,
)
: null,
requester: ApprovalRequesterDto.fromJson(
(json['requester'] as Map<String, dynamic>? ?? const {}),
),
requestedAt: _parseDate(json['requested_at']) ?? DateTime.now(),
decidedAt: _parseDate(json['decided_at']),
note: json['note'] as String?,
isActive: (json['is_active'] as bool?) ?? true,
isDeleted: (json['is_deleted'] as bool?) ?? false,
steps: (json['steps'] as List<dynamic>? ?? [])
.whereType<Map<String, dynamic>>()
.map(ApprovalStepDto.fromJson)
.toList(),
histories: (json['histories'] as List<dynamic>? ?? [])
.whereType<Map<String, dynamic>>()
note: json['note'] as String? ?? approvalEnvelope['note'] as String?,
isActive:
(json['is_active'] as bool?) ??
(approvalEnvelope['is_active'] as bool?) ??
true,
isDeleted:
(json['is_deleted'] as bool?) ??
(approvalEnvelope['is_deleted'] as bool?) ??
false,
steps: stepsSource.map(ApprovalStepDto.fromJson).toList(growable: false),
histories: historiesSource
.map(ApprovalHistoryDto.fromJson)
.toList(),
createdAt: _parseDate(json['created_at']),
updatedAt: _parseDate(json['updated_at']),
.toList(growable: false),
createdAt: _parseDate(
json['created_at'] ?? approvalEnvelope['created_at'],
),
updatedAt: _parseDate(
json['updated_at'] ?? approvalEnvelope['updated_at'],
),
);
}
@@ -123,9 +168,21 @@ class ApprovalStatusDto {
final String? color;
factory ApprovalStatusDto.fromJson(Map<String, dynamic> json) {
if (json['status'] is Map<String, dynamic>) {
return ApprovalStatusDto.fromJson(json['status'] as Map<String, dynamic>);
}
return ApprovalStatusDto(
id: json['id'] as int? ?? json['status_id'] as int? ?? 0,
name: json['name'] as String? ?? json['status_name'] as String? ?? '-',
id:
json['id'] as int? ??
json['status_id'] as int? ??
json['approval_status_id'] as int? ??
0,
name:
json['name'] as String? ??
json['status_name'] as String? ??
json['approval_status_name'] as String? ??
(json['status'] as String?) ??
'-',
color: json['color'] as String?,
);
}
@@ -216,6 +273,7 @@ class ApprovalStepDto {
status: ApprovalStatusDto.fromJson(
(json['status'] as Map<String, dynamic>? ??
json['step_status'] as Map<String, dynamic>? ??
json['approval_status'] as Map<String, dynamic>? ??
const {}),
),
assignedAt: _parseDate(json['assigned_at']) ?? DateTime.now(),
@@ -262,28 +320,41 @@ class ApprovalHistoryDto {
final String? note;
factory ApprovalHistoryDto.fromJson(Map<String, dynamic> json) {
final actionMap = _firstNonEmptyMap([
json['action'],
json['approval_action'],
json['step_action'],
]);
final fromStatusMap = _firstNonEmptyMap([
json['from_status'],
json['fromStatus'],
]);
final toStatusMap = _firstNonEmptyMap([
json['to_status'],
json['toStatus'],
]);
final approverMap = _firstNonEmptyMap([json['approver'], json['employee']]);
final fallbackAction = {
'id': json['approval_action_id'] ?? json['action_id'],
'name':
json['approval_action_name'] ??
json['action_name'] ??
(json['action'] as String?) ??
'-',
};
return ApprovalHistoryDto(
id: json['id'] as int?,
action: ApprovalActionDto.fromJson(
json['action'] is Map<String, dynamic>
? json['action'] as Map<String, dynamic>
: {
'id': json['approval_action_id'],
'name': json['approval_action_name'],
},
actionMap.isEmpty ? fallbackAction : actionMap,
),
fromStatus: json['from_status'] is Map<String, dynamic>
? ApprovalStatusDto.fromJson(
json['from_status'] as Map<String, dynamic>,
)
: null,
toStatus: ApprovalStatusDto.fromJson(
(json['to_status'] as Map<String, dynamic>? ?? const {}),
),
approver: ApprovalApproverDto.fromJson(
(json['approver'] as Map<String, dynamic>? ?? const {}),
),
actionAt: _parseDate(json['action_at']) ?? DateTime.now(),
fromStatus: fromStatusMap.isEmpty
? null
: ApprovalStatusDto.fromJson(fromStatusMap),
toStatus: ApprovalStatusDto.fromJson(toStatusMap),
approver: ApprovalApproverDto.fromJson(approverMap),
actionAt:
_parseDate(json['action_at'] ?? json['actionAt']) ?? DateTime.now(),
note: json['note'] as String?,
);
}
@@ -308,9 +379,21 @@ class ApprovalActionDto {
final String name;
factory ApprovalActionDto.fromJson(Map<String, dynamic> json) {
if (json['action'] is Map<String, dynamic>) {
return ApprovalActionDto.fromJson(json['action'] as Map<String, dynamic>);
}
return ApprovalActionDto(
id: json['id'] as int? ?? json['action_id'] as int? ?? 0,
name: json['name'] as String? ?? json['action_name'] as String? ?? '-',
id:
json['id'] as int? ??
json['action_id'] as int? ??
json['approval_action_id'] as int? ??
0,
name:
json['name'] as String? ??
json['action_name'] as String? ??
json['approval_action_name'] as String? ??
(json['action'] as String?) ??
'-',
);
}
@@ -318,6 +401,39 @@ class ApprovalActionDto {
ApprovalAction toEntity() => ApprovalAction(id: id, name: name);
}
List<Map<String, dynamic>> _asListOfMap(dynamic value) {
if (value is List) {
return value.whereType<Map<String, dynamic>>().toList(growable: false);
}
return const [];
}
Map<String, dynamic> _mapOrEmpty(dynamic value) =>
value is Map<String, dynamic> ? value : const {};
Map<String, dynamic> _firstNonEmptyMap(List<dynamic> candidates) {
for (final candidate in candidates) {
if (candidate is Map<String, dynamic> && candidate.isNotEmpty) {
return candidate;
}
}
return const {};
}
String? _pickString(List<dynamic> sources, List<String> keys) {
for (final source in sources) {
if (source is Map<String, dynamic>) {
for (final key in keys) {
final value = source[key];
if (value is String && value.isNotEmpty) {
return value;
}
}
}
}
return null;
}
/// 문자열/DateTime 입력을 DateTime으로 변환한다.
DateTime? _parseDate(Object? value) {
if (value == null) return null;