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
This commit is contained in:
167
lib/features/approvals/domain/entities/approval_flow.dart
Normal file
167
lib/features/approvals/domain/entities/approval_flow.dart
Normal file
@@ -0,0 +1,167 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user