- 환경/라우터 모듈에 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
168 lines
4.8 KiB
Dart
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;
|
|
}
|
|
}
|