feat(approvals): 결재 접근 차단 대응과 전표 전이 메모 전달 강화

- approvals 모듈에서 APPROVAL_ACCESS_DENIED 응답을 포착하여 ApprovalAccessDeniedException으로 변환하고 접근 거부 시 토스트·대시보드 리다이렉트를 처리

- approval history 조회가 서버 action id에 맞춰 필터링되도록 repository·controller·테스트를 보강

- 재고 트랜잭션 상태 전이 API 호출에 note를 전달하도록 repository·컨트롤러·통합/단위 테스트를 업데이트

- 승인 플로우 QA 체크리스트 및 연동 문서를 최신 계약과 테스트 흐름으로 업데이트
This commit is contained in:
JiWoong Sul
2025-10-31 16:43:14 +09:00
parent d76f765814
commit 3e83408aa7
35 changed files with 1056 additions and 470 deletions

View File

@@ -9,6 +9,7 @@ import 'package:superport_v2/core/common/models/paginated_result.dart';
import 'package:superport_v2/core/permissions/permission_manager.dart';
import 'package:superport_v2/features/approvals/domain/entities/approval.dart';
import 'package:superport_v2/features/approvals/domain/entities/approval_template.dart';
import 'package:superport_v2/features/approvals/domain/errors/approval_access_denied_exception.dart';
import 'package:superport_v2/features/approvals/domain/repositories/approval_repository.dart';
import 'package:superport_v2/features/approvals/domain/repositories/approval_template_repository.dart';
import 'package:superport_v2/features/approvals/presentation/controllers/approval_controller.dart';
@@ -111,6 +112,8 @@ void main() {
transactionId: any(named: 'transactionId'),
approvalStatusId: any(named: 'approvalStatusId'),
requestedById: any(named: 'requestedById'),
statusCodes: any(named: 'statusCodes'),
includePending: any(named: 'includePending'),
includeHistories: any(named: 'includeHistories'),
includeSteps: any(named: 'includeSteps'),
),
@@ -137,5 +140,27 @@ void main() {
await tester.pumpAndSettle();
expect(find.text('승인대기'), findsWidgets);
});
testWidgets('접근 거부 시 경고 토스트를 노출한다', (tester) async {
when(
() => repository.list(
page: any(named: 'page'),
pageSize: any(named: 'pageSize'),
transactionId: any(named: 'transactionId'),
approvalStatusId: any(named: 'approvalStatusId'),
requestedById: any(named: 'requestedById'),
statusCodes: any(named: 'statusCodes'),
includePending: any(named: 'includePending'),
includeHistories: any(named: 'includeHistories'),
includeSteps: any(named: 'includeSteps'),
),
).thenThrow(const ApprovalAccessDeniedException(message: '열람 권한이 없습니다.'));
await tester.pumpWidget(_buildApp(const ApprovalPage()));
await tester.pump();
await tester.pump(const Duration(milliseconds: 50));
expect(find.text('열람 권한이 없습니다.'), findsWidgets);
});
});
}