Files
superport_v2/lib/features/approvals/domain/entities/approval_flow.dart
JiWoong Sul d76f765814 feat(approvals): Approval Flow v2 프런트엔드 전면 개편
- 환경/라우터 모듈에 approval_flow_v2 토글을 추가하고 FeatureFlags 초기화를 연결 (.env*, lib/core/**)
- ApiClient 빌더·ApiRoutes 확장과 ApprovalRepositoryRemote 리팩터링으로 include·액션 시그니처를 정합화
- ApprovalFlow·ApprovalDraft 엔티티/레포/유즈케이스를 도입해 서버 초안과 단계 액션(승인·회수·재상신)을 지원
- Approval 컨트롤러·히스토리·템플릿 페이지와 공유 위젯을 재작성해 감사 로그·회수 UX·템플릿 CRUD를 반영
- Inbound/Outbound/Rental 컨트롤러·페이지에 결재 섹션을 삽입하고 대시보드 pending 카드 요약을 갱신
- SuperportDialog·FormField 등 공통 위젯을 보강하고 승인 위젯 가이드를 추가해 UI 가이드를 정리
- 결재/재고 테스트 픽스처와 단위·위젯·통합 테스트를 확장하고 flutter_test_config로 스테이징 호스트를 허용
- Approval Flow 레포트/플랜 문서를 업데이트하고 ApprovalFlow_System_Integration_and_ChangePlan.md를 추가
- 실행: flutter analyze, flutter test
2025-10-31 01:05:39 +09:00

168 lines
4.8 KiB
Dart

import '../entities/approval.dart';
/// 결재 흐름(Approval Flow)을 표현하는 도메인 엔티티.
///
/// - 상신자, 최종 승인자, 단계 목록, 이력, 상태 요약을 한 번에 제공한다.
/// - presentation 레이어에서는 이 엔티티만 의존해 UI를 구성한다.
class ApprovalFlow {
ApprovalFlow({
required Approval approval,
ApprovalApprover? finalApprover,
ApprovalFlowStatusSummary? statusSummary,
}) : _approval = approval,
finalApprover = finalApprover ?? _inferFinalApprover(approval.steps),
statusSummary =
statusSummary ??
ApprovalFlowStatusSummary.from(
status: approval.status,
steps: approval.steps,
currentStep: approval.currentStep,
),
_steps = List<ApprovalStep>.unmodifiable(approval.steps),
_histories = List<ApprovalHistory>.unmodifiable(approval.histories);
/// 결재 원본 데이터
final Approval _approval;
/// 결재 단계 목록
final List<ApprovalStep> _steps;
/// 결재 이력 목록
final List<ApprovalHistory> _histories;
/// 최종 승인자 정보 (단계 목록 기반 추론 결과)
final ApprovalApprover? finalApprover;
/// 결재 상태 요약 정보
final ApprovalFlowStatusSummary statusSummary;
/// 원본 결재 엔티티에 접근한다.
Approval get approval => _approval;
/// 결재 식별자(ID)
int? get id => _approval.id;
/// 결재 번호(APP-YYYYMMDDNNNN 형식)
String get approvalNo => _approval.approvalNo;
/// 연동된 전표 번호
String get transactionNo => _approval.transactionNo;
/// 연동된 전표 ID
int? get transactionId => _approval.transactionId;
/// 연동된 전표 최신 수정 시각
DateTime? get transactionUpdatedAt => _approval.transactionUpdatedAt;
/// 현재 결재 상태
ApprovalStatus get status => _approval.status;
/// 현재 진행 중인 단계 정보
ApprovalStep? get currentStep => _approval.currentStep;
/// 상신자 정보
ApprovalRequester get requester => _approval.requester;
/// 상신 일시
DateTime get requestedAt => _approval.requestedAt;
/// 최종 결정 일시
DateTime? get decidedAt => _approval.decidedAt;
/// 결재 메모
String? get note => _approval.note;
/// 생성 일시
DateTime? get createdAt => _approval.createdAt;
/// 변경 일시
DateTime? get updatedAt => _approval.updatedAt;
/// 단계 목록을 반환한다.
List<ApprovalStep> get steps => _steps;
/// 이력 목록을 반환한다.
List<ApprovalHistory> get histories => _histories;
/// [Approval] 엔티티에서 [ApprovalFlow]를 생성하는 팩토리.
factory ApprovalFlow.fromApproval(Approval approval) =>
ApprovalFlow(approval: approval);
static ApprovalApprover? _inferFinalApprover(List<ApprovalStep> steps) {
if (steps.isEmpty) {
return null;
}
final sorted = List<ApprovalStep>.from(steps)
..sort((a, b) => a.stepOrder.compareTo(b.stepOrder));
return sorted.last.approver;
}
}
/// 결재 상태 요약 정보.
///
/// - 전체 단계 수, 완료 단계 수, 대기 단계 수, 현재 단계 순번을 제공한다.
class ApprovalFlowStatusSummary {
ApprovalFlowStatusSummary({
required this.status,
required this.totalSteps,
required this.completedSteps,
required this.pendingSteps,
this.currentStepOrder,
});
/// 전체 결재 상태
final ApprovalStatus status;
/// 총 단계 수
final int totalSteps;
/// 완료된 단계 수
final int completedSteps;
/// 대기 중인 단계 수
final int pendingSteps;
/// 현재 진행 중인 단계 순번 (없으면 null)
final int? currentStepOrder;
/// 완료율(%)을 정수로 반환한다.
int get completionRate {
if (totalSteps <= 0) {
return 0;
}
final ratio = (completedSteps / totalSteps) * 100;
return ratio.isFinite ? ratio.round() : 0;
}
/// 결재 상태와 단계 목록을 기반으로 요약 정보를 생성한다.
factory ApprovalFlowStatusSummary.from({
required ApprovalStatus status,
required List<ApprovalStep> steps,
ApprovalStep? currentStep,
}) {
final total = steps.length;
final completed = steps.where((step) => step.decidedAt != null).length;
final pending = total - completed;
final currentOrder = currentStep?.stepOrder ?? _findCurrentStepOrder(steps);
return ApprovalFlowStatusSummary(
status: status,
totalSteps: total,
completedSteps: completed,
pendingSteps: pending < 0 ? 0 : pending,
currentStepOrder: currentOrder,
);
}
static int? _findCurrentStepOrder(List<ApprovalStep> steps) {
for (final step in steps) {
if (step.decidedAt == null) {
return step.stepOrder;
}
}
if (steps.isEmpty) {
return null;
}
return steps.last.stepOrder;
}
}