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:
JiWoong Sul
2025-10-31 01:05:39 +09:00
parent 259b056072
commit d76f765814
133 changed files with 13878 additions and 947 deletions

View File

@@ -150,5 +150,56 @@ void main() {
expect(client.unwrap(response), {'status': 'ok'});
});
});
group('buildPath', () {
test('선행 슬래시를 유지하며 세그먼트를 결합한다', () {
final path = ApiClient.buildPath('/api/v1', ['approvals', 1, 'steps']);
expect(path, '/api/v1/approvals/1/steps');
});
test('세그먼트의 중복 슬래시를 제거한다', () {
final path = ApiClient.buildPath('/api/v1/', ['/approval/', '/submit']);
expect(path, '/api/v1/approval/submit');
});
});
group('buildQuery', () {
test('페이지네이션/검색 파라미터를 정규화한다', () {
final now = DateTime.utc(2025, 1, 5, 12, 30);
final query = ApiClient.buildQuery(
page: 2,
pageSize: 50,
q: ' 품번 ',
sort: ' transaction_date ',
order: ' DESC ',
include: const ['steps', 'histories', 'steps'],
updatedSince: now,
filters: {
'transaction_id': 10,
'status': ' 진행 ',
'keyword': ' ',
'from': DateTime.utc(2025, 1, 1, 9),
'ids': [' 1 ', null, '2'],
},
);
expect(query['page'], 2);
expect(query['page_size'], 50);
expect(query['q'], '품번');
expect(query['sort'], 'transaction_date');
expect(query['order'], 'desc');
expect(query['include'], 'steps,histories');
expect(query['updated_since'], now.toUtc().toIso8601String());
expect(query['transaction_id'], 10);
expect(query['status'], '진행');
expect(query.containsKey('keyword'), isFalse);
expect(
query['from'],
DateTime.utc(2025, 1, 1, 9).toUtc().toIso8601String(),
);
expect(query['ids'], '1,2');
expect(() => query['page'] = 1, throwsUnsupportedError);
});
});
});
}

View File

@@ -92,9 +92,7 @@ void main() {
requestOptions: requestOptions,
statusCode: 401,
data: {
'error': {
'message': 'invalid credentials',
},
'error': {'message': 'invalid credentials'},
},
),
);