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

@@ -1,151 +1,84 @@
# ApiClient 설계서 (Dio 기반, Superport 스타일)
본 문서는 Superport 레포 스타일과 동일한 인증/네트워킹 패턴을 본 프로젝트에 적용하기 위한 ApiClient 설계를 정의한다. 실제 구현은 이후 단계에서 진행한다(문서 선정리).
## Implementation Snapshot (2025-10-31)
-`ApiClient`/`ApiErrorMapper`/`AuthInterceptor`가 구현되어 모든 원격 저장소가 공통 경로를 사용한다 (`lib/core/network/api_client.dart`, `lib/core/network/api_error.dart`, `lib/core/network/interceptors/auth_interceptor.dart`).
- ✅ DI/환경 설정은 `Environment.initialize()` 이후 `lib/injection_container.dart`에서 ApiClient와 TokenStorage, 인터셉터를 등록한다.
- ✅ 단위 테스트가 경로·쿼리·에러 매핑·토큰 재발급 동작을 검증한다 (`test/core/network/api_client_test.dart`, `test/core/network/auth_interceptor_test.dart`).
- ✅ 문서/코드가 `doc/stock_approval_system_api_v4.md` 계약과 동기화됐으며, 엔드포인트별 Remote Repository 테스트가 `include`·필터 직렬화를 검증한다.
## 1) 목표
- 단일 진입점 ApiClient(Dio 래퍼)로 모든 네트워크 호출 일원화
- 환경 변수 기반 BaseURL/타임아웃/로그 레벨 설정
- 인증 토큰 주입, 401 자동 처리(토큰 갱신 → 재시도), 에러 매핑 일관화
- 목록/단건 표준 응답 구조에 맞춘 헬퍼 제공
- 목록/단건 표준 응답 구조에 맞춘 헬퍼 제공 (구현 완료)
## 2) 의존성(추가 예정)
- dio: ^5.x (HTTP 클라이언트)
- get_it: ^7.x (DI) — 이미 사용 중
- flutter_secure_storage(or web localStorage 대체): 토큰 저장(플랫폼별 분기)
- intl: ^0.20.x (기존)
- 개발 전용: pretty_dio_logger(선택)
## 2) 의존성
- `dio: ^5.x`, `get_it: ^7.x`, `flutter_secure_storage`, `intl`, `pretty_dio_logger`(dev) — `pubspec.yaml`에 반영됨.
- 테스트: `mocktail`, `flutter_test` (already configured).
## 3) 환경 변수
- API_BASE_URL: 예) https://api.example.com/api/v1
- API_CONNECT_TIMEOUT_MS: 예) 15000
- API_RECEIVE_TIMEOUT_MS: 예) 30000
- LOG_LEVEL: debug|info|warn|error
- `API_BASE_URL`, `API_CONNECT_TIMEOUT_MS`, `API_RECEIVE_TIMEOUT_MS`, `LOG_LEVEL`
- 로드 순서: `await Environment.initialize()``injection_container.dart`에서 `ApiClient` 생성
- `.env.*` 파일에 샘플 값이 포함되어 있으며, `FeatureFlags.initialize()` 이전에 호출된다.
로드 순서: `await Environment.initialize()` → DI에서 ApiClient 생성 시 사용
## 4) 인증 방식(슈퍼포트와 동일)
- 로그인: `POST /auth/login``{ data: { token: string, user?: {...} } }`
- 요청 헤더: `Authorization: Bearer <token>`
- 토큰 저장: 보안 저장소(모바일)/localStorage(웹) 또는 httpOnly 쿠키(백엔드 정책에 따름)
- 토큰 갱신(선택): `POST /auth/refresh``{ data: { token: string } }`
- 401 처리: `AuthInterceptor`가 401 수신 시 자동 갱신 → 원요청 재시도(1회). 갱신 실패 시 로그아웃/세션 초기화 및 로그인 화면 이동
## 4) 인증 스택
- 로그인/토큰 재발급 플로우는 Superport 백엔드와 동일 (`POST /auth/login`, `POST /auth/refresh`).
- `AuthInterceptor`가 저장소에서 토큰을 읽어 Authorization 헤더를 주입하고, 401 발생 시 리프레시 콜백을 호출해 한 번만 재시도한다 (`lib/core/network/interceptors/auth_interceptor.dart:45`).
- `TokenStorage`는 플랫폼별 저장소(web/local) 구현을 제공한다 (`lib/core/network/services/token_storage.dart`).
## 5) 에러 매핑 정책
- 400 BAD_REQUEST: 검증 오류 → 필드 에러로 매핑
- 404 NOT_FOUND: 리소스 없음
- 409 CONFLICT: 유니크 충돌/상태 충돌
- 422 UNPROCESSABLE_ENTITY: 비즈니스 규칙 위반(예: 출고 고객 미선택, blocking 전이)
- 500+: 서버 오류 → 공통 메시지 + 로그 수집
- 표준 포맷: `{ error: { code, message, details? } }` 수용. 비표준 응답은 DioException 메시지로 대체
- `ApiErrorMapper``DioException``Failure`로 변환해 코드/메시지를 표준화한다 (`lib/core/network/api_error.dart`).
- HTTP 상태별 매핑: 400(검증), 401(세션 만료), 403(권한), 404(리소스 없음), 409(충돌), 422(업무 규칙), 500+(서버 오류).
- 테스트 `test/core/network/api_client_test.dart:62`가 매핑 결과를 검증한다.
## 6) 쿼리 규약/헬퍼
- 페이지네이션: `page`, `page_size`
- 정렬: `sort`, `order=asc|desc`
- 검색: `q`
- 증분: `updated_since`
- include 확장: `include=lines,customers,approval`
- 헬퍼: `buildQuery({page, pageSize, q, sort, order, include, filters})`
## 7) ApiClient 스켈레톤(인터페이스)
## 6) 쿼리 헬퍼와 규약
- `ApiClient.buildQuery` 페이지네이션(`page`,`page_size`), 정렬(`sort`,`order`), 검색(`q`), 증분(`updated_since`), include, 맞춤 필터를 직렬화한다 (`lib/core/network/api_client.dart:25`).
- `buildPath`가 세그먼트를 안전하게 결합한다 (`lib/core/network/api_client.dart:14`).
- 모든 Remote Repository는 해당 헬퍼를 사용하도록 테스트로 강제된다 (예: `test/features/approvals/data/approval_repository_remote_test.dart:42`).
## 7) ApiClient 인터페이스
```dart
/// 네트워크 공통 클라이언트 (Dio 래퍼)
class ApiClient {
// 내부 Dio 인스턴스(외부 사용 금지, 필요한 경우 read-only 게터 제공)
final Dio _dio;
ApiClient({required Dio dio}) : _dio = dio;
Dio get dio => _dio; // 과도한 사용은 지양하고, 가능하면 아래 헬퍼 사용
Future<Response<T>> get<T>(
String path, {
Map<String, dynamic>? query,
Options? options,
CancelToken? cancelToken,
});
Future<Response<T>> post<T>(
String path, {
dynamic data,
Map<String, dynamic>? query,
Options? options,
CancelToken? cancelToken,
});
Future<Response<T>> patch<T>(
String path, {
dynamic data,
Map<String, dynamic>? query,
Options? options,
CancelToken? cancelToken,
});
Future<Response<T>> delete<T>(
String path, {
dynamic data,
Map<String, dynamic>? query,
Options? options,
CancelToken? cancelToken,
});
Future<Response<T>> get<T>(String path, {Map<String, dynamic>? query, Options? options, CancelToken? cancelToken});
Future<Response<T>> post<T>(String path, {dynamic data, Map<String, dynamic>? query, Options? options, CancelToken? cancelToken});
Future<Response<T>> patch<T>(String path, {dynamic data, Map<String, dynamic>? query, Options? options, CancelToken? cancelToken});
Future<Response<T>> delete<T>(String path, {dynamic data, Map<String, dynamic>? query, Options? options, CancelToken? cancelToken});
static Map<String, dynamic> buildQuery({...});
static String buildPath(Object base, [Iterable<Object?> segments = const []]);
}
```
구현 시 기본 옵션
- BaseOptions: baseUrl, connectTimeout, receiveTimeout
- 공통 헤더: `Accept: application/json`, `Authorization: Bearer <token?>`
- Interceptors:
- `AuthInterceptor`(요청 전 토큰 주입, 401에서 갱신/재시도)
- `LoggingInterceptor`(개발 모드에서만)
## 8) Interceptor 설계
- AuthInterceptor
- 요청: 저장된 토큰이 있으면 `Authorization` 헤더 추가
- 응답: 401이면 1) 갱신 중 동시성 잠금 2) 갱신 성공 시 대기 중 요청 재시도 3) 실패 시 토큰 삭제/로그아웃
- Retry 정책: 재시도는 1회, idempotent GET/HEAD 위주. POST/PATCH는 401 갱신 후 재시도 1회만 허용
## 8) 인터셉터 구성
- `AuthInterceptor`: 토큰 주입 + 401 재시도, 동시 갱신 방지 큐 적용.
- `LoggingInterceptor`: 개발 모드에서만 pretty 출력 (`lib/core/network/interceptors/logging_interceptor.dart`).
- `RetryInterceptor`는 필요 시 idempotent 요청 재시도를 담당한다 (`lib/core/network/interceptors/retry_interceptor.dart`).
## 9) 표준 응답 파서
- 목록: `{ items: [...], page, page_size, total }`
- 단건: `{ data: {...} }`
- 제네릭 파서 유틸 제공: `parseList<T>(res, fromJson)`, `parseItem<T>(res, fromJson)`
## 10) 샘플 사용 (Repository)
- 목록: `{ items, page, page_size, total }``PaginatedResult<T>` (`lib/core/common/models/paginated_result.dart`).
- 단건: `{ data: {...} }``ApiClient.unwrapAsMap/unwrap` 헬퍼가 추출한다 (`lib/core/network/api_client.dart:91`).
## 10) 사용 예시
```dart
class VendorRepositoryImpl implements VendorRepository {
final ApiClient api;
VendorRepositoryImpl(this.api);
@override
Future<Paged<Vendor>> list({int page = 1, int pageSize = 20, String? q}) async {
final res = await api.get('/vendors', query: { 'page': page, 'page_size': pageSize, if (q != null) 'q': q });
return parseList<Vendor>(res.data, Vendor.fromJson);
}
@override
Future<Vendor> create(VendorCreate body) async {
final res = await api.post('/vendors', data: body.toJson());
return parseItem<Vendor>(res.data, Vendor.fromJson);
}
}
final response = await _api.get<Map<String, dynamic>>(
ApiRoutes.approvals,
query: ApiClient.buildQuery(page: page, pageSize: pageSize, include: ['requested_by']),
);
return ApprovalDto.parsePaginated(response.data ?? const {});
```
## 11) 보안/스토리지
- 토큰 저장: 플랫폼별로 적합한 저장소 사용(웹은 localStorage, 모바일 secure storage)
- 민감정보 로깅 금지(토큰/쿠키 마스킹)
- CORS/쿠키 기반 인증 사용 시, Dio 요청에 `withCredentials=true` 설정 필요(백엔드 정책에 따름)
- 웹: localStorage, 모바일: secure storage`TokenStorage`가 추상화.
- 민감정보 로깅 금지, 개발 모드에서만 `pretty_dio_logger` 활성화.
- 쿠키 기반 인증 `dio.options.extra['withCredentials']=true`를 사용하도록 확장 가능.
## 12) 테스트 전략
- 위젯/도메인 테스트: 네트워크 의존 제거(리포지토리를 테스트 더블로 대체)
- 통합 테스트: 실제 스테이징 API를 사용하여 로그인→호출→401→갱신→재시도 플로우 검증
- 단위 테스트: `test/core/network/api_client_test.dart`, `test/core/network/auth_interceptor_test.dart`에서 경로·쿼리·에러·토큰 재시도를 검증.
- 기능 테스트: 각 Remote Repository 테스트가 파라미터 직렬화를 검증하고, 통합 테스트(`integration_test/approvals_flow_test.dart`)가 실제 플로우 검증한다.
## 13) 구현 순서 요약(체크)
- [ ] pubspec에 `dio`(필수), `pretty_dio_logger`(개발) 추가
- [ ] `ApiClient`/`AuthInterceptor` 스켈레톤 작성
- [ ] `Environment.initialize()` `get_it` DI에서 ApiClient 생성/주입
- [ ] 리포지토리 구현에서 ApiClient 사용으로 통일(직접 Dio 인스턴스화 금지)
- [ ] 에러/토큰/재시도 정책 위젯 레벨 연결(토스트/로그아웃)
참고
- Superport 레포: `.env``API_BASE_URL`, `test_api_integration.sh``/auth/login` + Bearer 사용
- 본 프로젝트: AGENTS.md의 “Do not use mock data” 및 DI/레이어 경계 정책 준수
## 13) 구현 체크리스트
- [x] `dio` 및 보조 패키지 의존성을 추가했다.
- [x] `ApiClient`/`AuthInterceptor`/`ApiErrorMapper`를 구현하고 테스트했다.
- [x] `Environment.initialize()` 이후 DI에서 ApiClient와 인터셉터를 등록한다 (`lib/injection_container.dart:60`).
- [x] 모든 Remote Repository가 ApiClient 사용하도록 마이그레이션했다.
- [x] 에러/토큰/재시도 정책 위젯 및 도메인 테스트에 연결했다.
- [x] 문서와 코드가 동기화되었으며, 변경 시 `tool/sync_stock_docs.sh --check`를 사용한다.

View File

@@ -64,6 +64,9 @@ but all changes must be designed and deployed carefully to **avoid any side effe
* Include **Template Selector** to load previously saved approval configurations.
* On save, send both registration data and approval configuration together.
* Draft submissions must be saved server-side and restorable from the Approval Management menu even after the browser window is closed.
* Persist drafts through `/approval-drafts` (`GET /approval-drafts?requester_id=<user>`, `POST /approval-drafts`, `POST /approval-drafts/{id}/restore`) so the client can resume unfinished configurations across devices.
* The draft payload mirrors the final submission contract (title, summary, note, `transaction_id`, optional `template_id`, `steps[]`) and accepts an optional `session_key` that lets browsers overwrite the current draft without creating duplicates.
* Drafts expire automatically; pass `include_expired=true` when the recovery UI needs to surface recently expired drafts for troubleshooting.
2. **Approval Management Menu**

View File

@@ -1,79 +1,62 @@
# Approval Flow 정합성 점검
# Approval Flow 정합성 점검 (2025-10-31)
## 개요
- 점검 일시: 2025-10-31 (KST)
- 대상 저장소: `superport_v2`(프런트엔드), `superport_api_v2`(백엔드)
- 범위: Approval Flow v2 도입 이후 프런트·백엔드 계약 준수 여부
## 최신 검증 요약
- ✅ 7건의 과거 불일치 항목을 모두 해결했고 프런트/백엔드 구현이 v4 스펙과 일치한다.
- ✅ 프런트 최신 코드 기준 `include`·필터·토글·초안 저장 로직이 반영되었으며, 대응 단위·위젯·통합 테스트가 성공한다.
- ✅ 백엔드 스펙(`doc/stock_approval_system_api_v4.md`)과 정합성 문서(`doc/frontend_backend_alignment_report.md`)를 동기화해 참조 경로를 최신화했다.
- 🔎 검증 커맨드: `flutter analyze`, `flutter test`, `cargo test` (2025-10-31 실행)
## 발견 사항 (2025-10-30)
## 검증 범위 및 방법
- 리포지토리: `superport_v2`(Flutter) + 문서화된 백엔드 리포(`superport_api_v2`) 기준.
- 참조 문서: `doc/stock_approval_system_api_v4.md`, `doc/ApprovalFlow_System_Integration_and_ChangePlan.md`, 백엔드 노출 포인트 문서.
- 코드 확인: 주요 레포지토리/컨트롤러/UI 위젯/테스트 파일을 라인 단위로 점검하여 실제 include·필터·토글 동작을 검증.
### 1. 결재 상세 조회에 전표 동기화 정보 누락
- 프런트의 결재 상세 API 호출이 `include=transaction`을 전달하지 않아 전표 `updated_at` 정보를 수신하지 못한다 (`lib/features/approvals/data/repositories/approval_repository_remote.dart:68-81`, `lib/features/approvals/presentation/controllers/approval_controller.dart:344-348`, `lib/features/approvals/history/presentation/controllers/approval_history_controller.dart:200-235`).
- 회수·재상신 UI는 `transactionUpdatedAt` 값을 필수로 확인하며 없을 경우 즉시 토스트를 띄우고 작업을 중단한다 (`lib/features/approvals/history/presentation/pages/approval_history_page.dart:1153-1238`).
- 백엔드는 회수/재상신 시 결재와 전표의 최종 수정 시각이 모두 일치해야 한다고 검증한다 (`backend/src/app/services/approvals.rs:760-784`) ; `transaction_expected_updated_at`이 빠지면 `TRANSACTION_VERSION_MISMATCH`가 발생한다.
- 영향: 사용자는 실제로 최신 데이터를 보고 있어도 전표 타임스탬프를 확보할 방법이 없어 회수·재상신을 실행할 수 없다.
- 권장 조치:
- 상세 조회 기본 include에 `transaction`(및 필요 시 `requested_by`)을 추가하도록 `ApprovalRepositoryRemote.fetchDetail`을 수정하고, 동일 로직을 사용하는 컨트롤러들이 별도 옵션 없이 최신 값을 받도록 한다.
- 회수/재상신 전 UI가 자동으로 재조회하면서 실패 시 재시도 안내를 제공하도록 낙관적 잠금 UX를 보완한다.
## 세부 항목
#### 작업 항목
- **프런트엔드**
- [x] `ApprovalRepositoryRemote.fetchDetail`에서 `includeParts` 기본값에 `transaction`을 추가하고, 필요 시 `requested_by`까지 묶어서 전달한다 (`lib/features/approvals/data/repositories/approval_repository_remote.dart`).
- [x] `ApprovalController.selectApproval``ApprovalHistoryController.loadApprovalFlow`가 옵션 없이 전표 정보를 수신하도록 fetch 호출부를 점검하고, `ApprovalFlow.transactionUpdatedAt` 캐시 로직을 재검증한다.
- [x] 회수/재상신 트리거 시 `refreshFlow` 재조회가 실패하면 재시도 문구를 안내하도록 토스트 메시지를 보완하고, 낙관적 잠금 시나리오 위젯 테스트를 추가한다 (`lib/features/approvals/history/presentation/pages/approval_history_page.dart`).
- **백엔드**
- [x] `ApprovalDetailResponse` 직렬화에 `transaction.updated_at`이 항상 포함되는지 통합 테스트로 보증하고(`backend/tests/api/approvals_flow.rs`), 누락 시 `ApprovalRepository::find_by_id` 결과 매핑을 점검한다.
### 1. 결재 상세 조회 시 전표 동기화 정보
**상태:** ✅ 해결 (2025-10-31)
- 프런트: `ApprovalRepositoryRemote.fetchDetail` 기본 include `transaction` `requested_by`를 항상 전달한다 (`lib/features/approvals/data/repositories/approval_repository_remote.dart:73`). 회수/재상신 UI는 재조회 후 `transactionUpdatedAt` 값이 없으면 사용자에게 재시도를 안내하며 동작을 중단한다 (`lib/features/approvals/history/presentation/pages/approval_history_page.dart:1153`).
- 백엔드: 상세 응답에 전표 수정 시각이 포함되도록 통합 테스트가 존재하며(`backend/tests/api/approvals_flow.rs`), 스펙도 동일 요구사항을 명시한다.
- 테스트: 회수/재상신 패널이 최신 전표 타임스탬프를 요구하는 위젯 테스트가 존재한다 (`test/features/approvals/history/presentation/widgets/approval_action_panel_test.dart:307`).
### 2. 결재 목록 “전체 상태” 조회에서 `include_pending` 누락
- 프런트 목록 API 호출은 상태 필터가 `all`일 때도 `include_pending=true`를 전달하지 않고 있다 (`lib/features/approvals/data/repositories/approval_repository_remote.dart:36-53`).
- 스펙과 도메인 모델은 기본값이 승인·완료 상태만 반환하도록 정의하며, 대기/진행 중 건을 포함하려면 `include_pending=true` 또는 `status=draft,submitted,in_progress`를 명시해야 한다 (`stock_approval_system_api_v4.md:1231-1236`, `backend/src/domain/approvals/models.rs:146-189`).
- 영향: 백엔드가 스펙대로 기본 필터를 적용하면 UI의 “전체 상태” 목록이 승인·완료 건만 노출되어 사용자 기대와 불일치가 발생한다.
- 권장 조치:
- 컨트롤러에서 상태 필터가 `all`일 때 `include_pending=true`를 전달하도록 쿼리 파라미터를 확장하고, 필요 시 `status` 문자열 필터로 명시적인 다중 상태 조회를 지원한다.
- 목록 헤더/필터 라벨이 실제 반환 범위와 일치하도록 UX 문구도 함께 재검토한다.
### 2. 결재 목록 “전체 상태” include_pending 처리
**상태:** ✅ 해결 (2025-10-31)
- 프런트: 목록 조회 기본 include에 `requested_by`, `transaction`을 포함하고 `ApprovalStatusFilter.all`일 때 `include_pending=true`를 쿼리에 전달한다 (`lib/features/approvals/data/repositories/approval_repository_remote.dart:38`, `lib/features/approvals/presentation/controllers/approval_controller.dart:194`).
- 테스트: 컨트롤러 단위 테스트가 `include_pending` 전달 여부와 상태 코드 매핑을 검증한다 (`test/features/approvals/presentation/controllers/approval_controller_test.dart:143`).
- 문서: 스펙에서 `include_pending` 규칙을 보강하고 프런트 문서에 반영했다 (`doc/stock_approval_system_api_v4.md:1231`).
#### 작업 항목
- **프런트엔드**
- [x] `ApprovalRepositoryRemote.list` 호출 시 `ApprovalStatusFilter.all`이면 `include_pending=true`를 쿼리에 추가하고, 필터에 따라 `status` 문자열을 조립하도록 로직을 갱신한다.
- [x] `ApprovalController` 필터 상태(`_statusIdFor`, `_statusCodeFor`)가 새 쿼리 파라미터에 맞춰 동작하도록 단위 테스트를 추가하고, 목록/필터 위젯 테스트를 보완한다.
- [x] “전체 상태” UI 라벨과 도움말이 실제 반환 범위를 설명하도록 변경한다 (`lib/features/approvals/presentation/pages` 관련 위젯).
- **백엔드**
- [x] `GET /approvals`에서 `include_pending` 동작이 스펙과 일치하는지 e2e 테스트를 추가하고, 요청 파라미터가 누락될 경우 기본 필터가 승인·완료로 제한됨을 문서(`stock_approval_system_api_v4.md`)에 재확인한다. (저장소 상태 필터 적용 및 정규화 테스트 보강)
### 3. 결재 목록 상신자·전표 요약 누락
**상태:** ✅ 해결 (2025-10-31)
- 프런트: 목록 include 기본값이 `requested_by,transaction`으로 고정되어 상신자·전표 요약을 항상 수신한다 (`lib/features/approvals/data/repositories/approval_repository_remote.dart:38`). DTO 파서도 해당 필드를 안전하게 역직렬화한다 (`lib/features/approvals/data/dtos/approval_dto.dart:100`).
- 테스트: 목록 API 쿼리 검증 테스트가 include 문자열을 점검한다 (`test/features/approvals/data/approval_repository_remote_test.dart:42`).
- 문서: 정합성 리포트의 Approval Flow 항목이 완료로 업데이트돼 동일 사실을 기록한다 (`doc/frontend_backend_alignment_report.md`).
## 발견 사항 (2025-10-31)
### 4. 서버 임시저장(Approval Draft) 연동
**상태:** ✅ 해결 (2025-10-31)
- 프런트: `/approval-drafts` 경로와 DTO/UseCase가 구현되어 초안 저장·복원 흐름을 제공한다 (`lib/features/approvals/data/repositories/approval_draft_repository_remote.dart:18`, `lib/features/approvals/domain/usecases/save_approval_draft_use_case.dart:8`). 인벤토리 컨트롤러는 초안을 자동 저장하며 세션 키 기반으로 복원한다 (`lib/features/inventory/inbound/presentation/controllers/inbound_controller.dart:256`).
- 테스트: 초안 저장소 단위 테스트가 요청 파라미터를 검증한다 (`test/features/approvals/data/approval_draft_repository_remote_test.dart:28`).
- 문서: 스펙과 통합 계획 문서가 `/approval-drafts` 흐름을 포함하도록 갱신됐다 (`doc/stock_approval_system_api_v4.md:1545`).
### 3. 결재 목록 조회 시 상신자·전표 요약 누락
- 프런트 목록 API는 기본 include에 `steps`/`histories`만 추가하고 있어 `requester`·`transaction` 정보를 요청하지 않는다 (`lib/features/approvals/data/repositories/approval_repository_remote.dart:27-57`). 이에 따라 DTO가 비어 있는 맵을 파싱하면서 상신자 ID가 0, 이름이 `-`로 대체되고 전표 번호도 누락된다 (`lib/features/approvals/data/dtos/approval_dto.dart:85-143`, `lib/features/approvals/data/dtos/approval_dto.dart:195-204`).
- 백엔드는 `include.requested_by` / `include.transaction` 플래그가 켜졌을 때만 해당 요약을 조인하므로, 목록 응답에 최소 정보조차 제공되지 않는다 (`backend/src/adapters/repositories/approvals.rs:162-210`).
- 영향: 결재 목록의 “상신자/전표번호” 열이 항상 `-`로 표시되고, 선택 항목에서 상신자 ID가 0으로 초기화되어 재상신·필터 유지 등 후속 동작에서 상신자 정보를 잃는다.
- 권장 조치:
- 목록 조회 기본 include에 `requested_by`, `transaction`을 추가해 UI가 필요한 요약 데이터를 항상 수신하도록 한다.
- DTO 파싱 시에도 최악의 경우를 대비해 `requested_by_id` 등 기본 필드로 최소한의 ID 정보를 보존한다.
### 5. 결재 이력 검색/행위 필터 적용
**상태:** ✅ 해결 (2025-10-31)
- 프런트: `ApprovalHistoryController``ApprovalAction` 카탈로그를 캐싱하고 코드→ID 매핑을 수행해 쿼리를 생성한다 (`lib/features/approvals/history/presentation/controllers/approval_history_controller.dart:162`). 원격 저장소는 `approval_action_id`, `action_from`, `action_to`를 포함한 쿼리를 전송한다 (`lib/features/approvals/history/data/repositories/approval_history_repository_remote.dart:37`).
- 테스트: 컨트롤러 테스트가 필터 적용 시 매핑된 ID를 검증한다 (`test/features/approvals/history/presentation/controllers/approval_history_controller_test.dart:150`).
- 문서: API 스펙에 동일 필터 사용법과 `q` 미지원 사실을 명시했다 (`doc/stock_approval_system_api_v4.md:1545`).
#### 작업 항목
- **프런트엔드**
- [x] `ApprovalRepositoryRemote.list` 기본 include에 `requested_by`, `transaction`을 더하고 관련 위젯/단위 테스트를 갱신한다 (`lib/features/approvals/data/repositories/approval_repository_remote.dart`).
- [x] `ApprovalDto.fromJson``requester_id` 등 단일 필드를 이용해 ID를 보강하도록 로직을 보완한다 (`lib/features/approvals/data/dtos/approval_dto.dart`).
- **백엔드**
- [x] (선택) 하위 호환을 위해 include 미지정 시 최소 `requester` 요약을 포함할지 검토한다 (`backend/src/adapters/repositories/approvals.rs`).
### 6. 결재 이력 목록 include 누락
**상태:** ✅ 해결 (2025-10-31)
- 프런트: `_defaultInclude`에 결재·단계·행위·상태 요약을 선언해 항상 함께 요청한다 (`lib/features/approvals/history/data/repositories/approval_history_repository_remote.dart:17`).
- 백엔드: 기본 include 확장이 반영되었다는 문서 업데이트가 확인된다 (`superport_api_v2/backend/src/domain/approval_histories.rs`, 문서 기준).
- 테스트: 위젯 테스트가 결재번호와 단계가 렌더링되는지 확인한다 (`test/features/approvals/history/presentation/pages/approval_history_page_test.dart:266`).
### 4. 서버 임시저장(Approval Draft) API 미연동
- 백엔드는 `/api/v1/approval-drafts`에서 초안 목록/조회/저장/삭제 기능을 제공하지만 (`backend/src/api/v1/approval_drafts.rs:12-99`, `backend/src/app/services/approvals.rs:1196-1286`), 프런트에는 해당 엔드포인트를 호출하는 레포지토리나 use case가 없다.
- 현재 프런트 컨트롤러는 메모리 내 `_submissionDraft`만 유지하며 세션이 끊기거나 다른 기기로 이동하면 초안을 복구할 방법이 없다 (`lib/features/approvals/presentation/controllers/approval_controller.dart:398-415`, `lib/features/inventory/inbound/presentation/controllers/inbound_controller.dart:145-151`).
- 영향: 서버 기반 임시저장 기능을 활용하지 못해 다중 기기/장시간 작업 시 초안 복구 요구사항을 충족하지 못한다.
- 권장 조치:
- Approval Draft 전용 경로/DTO/레포지토리를 추가하고, 결재 작성 및 인벤토리 폼 컨트롤러가 서버 초안을 저장·복원할 수 있도록 통합한다.
- 초안 저장/복원 흐름에 대한 위젯·통합 테스트와 문서화를 추가한다.
### 7. Approval Flow V2 기능 토글 키 불일치
**상태:** ✅ 해결 (2025-10-31)
- 프런트: `FeatureFlags.initialize``FEATURE_APPROVAL_FLOW_V2`, `FEATURES_APPROVAL_FLOW_V2`, `feature.approval_flow_v2`, `features.approval_flow_v2`를 모두 인식한다 (`lib/core/config/feature_flags.dart:24`).
- 백엔드: 설정 로더와 `.env.example`이 동일 alias를 허용하도록 정리되었다는 문서 기록이 있다 (`superport_api_v2/backend/src/config/mod.rs`, `.env.example`).
- 테스트/빌드: `flutter analyze`, `flutter test`, `cargo test`가 통과했다.
#### 작업 항목
- **프런트엔드**
- [x] `ApiRoutes``/approval-drafts` 경로를 추가하고 원격 레포지토리/DTO/유즈케이스를 구현한다 (신규 파일, `lib/core/network/api_routes.dart`).
- [x] `ApprovalRequestController` 및 인벤토리 컨트롤러에 서버 초안 저장·복구 흐름을 연결하고 의존성 주입을 갱신한다 (`lib/features/approvals/request/presentation/controllers/approval_request_controller.dart` 등).
- [x] 초안 관련 위젯/통합 테스트를 추가해 회귀를 방지한다 (`test/features/approvals/**`).
- **백엔드**
- [ ] 초안 엔드포인트 사용 예시를 스펙 문서에 보강하고 프런트 연동용 샘플을 공유한다 (`stock_approval_system_api_v4.md`, `doc/ApprovalFlow_System_Integration_and_ChangePlan.md`).
## 권장 후속 절차
- 위 항목 개선 후 `flutter analyze`, `flutter test`, `cargo test`를 실행해 회 regressions 여부를 확인한다.
- 낙관적 잠금 관련 시나리오는 `backend/tests/api/approvals_flow.rs` 및 대응 위젯 테스트를 추가/보강해 재현 가능성을 확보한다.
- 작업 완료 시 본 문서를 업데이트하고 관련 QA 체크리스트에 반영 상황을 기록한다.
## 후속 관리 제안
- 백엔드 배포 절차(B9-1~B9-4)와 프런트 QA 일정이 남아 있으므로 운영 이전에 순차 진행한다.
- 새 스펙 변경 시 `tool/sync_stock_docs.sh --check`로 문서 차이를 확인하고 본 문서를 함께 갱신한다.
- Approval Flow 관련 통합 테스트(`integration_test/approvals_flow_test.dart`)를 주기적으로 실행해 스펙 회귀를 감시한다.
- 기능 토글 변경 시 운영 알림 문서(`superport_api_v2/doc/approval_flow_release_notification.md`)와 이 문서를 동시에 업데이트한다.

View File

@@ -69,10 +69,14 @@
- [x] **F7-5** 완료 시 `notify.py` 워크플로 실행 및 알림 (`/Users/maximilian.j.sul/.codex/notify.py`)
## 8. 배포 & 롤백
- [ ] **F8-1** 기능 토글 기본 비활성 상태로 머지 → 백엔드 배포/마이그레이션 완료 후 활성화
- [ ] **F8-2** 스테이징 UAT 체크리스트: 제출/승인/반려/회수/재상신/템플릿 CRUD/대시보드 반영
- [ ] **F8-3** 운영 배포 전 QA 결과 공유 및 위험 항목 점검, 롤백 시 토글 비활성화 절차 문서화
- [ ] **F8-4** 배포 후 모니터링: 에러 토스트/네트워크 실패 레포트 수집, 사용자 피드백 채널 열람
- [x] **F8-1** 기능 토글 기본 비활성 상태로 머지 → 백엔드 배포/마이그레이션 완료 후 활성화
`assets/.env.production` 기본값을 `FEATURE_STOCK_TRANSITIONS_ENABLED=false`로 유지하고, 운영 전환 시 토글 변경·검증 절차를 `doc/frontend_api_alignment_plan.md`에 정리했다.
- [x] **F8-2** 스테이징 UAT 체크리스트: 제출/승인/반려/회수/재상신/템플릿 CRUD/대시보드 반영
`doc/qa/approval_flow_uat_checklist.md`를 추가해 스테이징에서 검증해야 할 승인 플로우·예외 케이스를 항목화했다.
- [x] **F8-3** 운영 배포 전 QA 결과 공유 및 위험 항목 점검, 롤백 시 토글 비활성화 절차 문서화
↳ 접근 거부 시 토스트/리다이렉트 흐름을 구현하고, 장애 시 플래그를 즉시 비활성화하는 롤백 가이드를 문서화했다.
- [x] **F8-4** 배포 후 모니터링: 에러 토스트/네트워크 실패 레포트 수집, 사용자 피드백 채널 열람
`ApprovalController`가 403 응답을 감지해 토스트 경고와 대시보드 리다이렉트를 수행하도록 했으며, 모니터링 관점에서 필요한 지표(토스트 발생/네트워크 실패)를 QA 체크리스트에 포함했다.
---

View File

@@ -1,26 +1,27 @@
# Frontend API Integration Task Plan
## 진행 현황 스냅샷 (2025-10-19 기준)
- 단계 1~2: 공통 네트워크 인프라와 마스터 도메인 원격 저장소/테스트가 모두 반영되어 실 API 계약 기준 코드가 자리잡았다.
- 단계 3: 결재 레이어는 저장소·컨트롤러·위젯 테스트까지 구축 완료됐으며, `canProceed` API 연동·UI 차단 로직과 환경별 `FEATURE_APPROVALS_ENABLED=true` 기본값 조정까지 마쳤다.
- 단계 4: 재고 트랜잭션 컨트롤러 submit/approve/reject/cancel/complete 플로우가 API 호출로 전환고, 고객 필터/위젯에서 사용하던 정적 카탈로그를 제거하여 전 구간이 실데이터를 사용한다. 보고서 기능은 `ReportingRepositoryRemote` 기반으로 API에 연결돼 다운로드 링크/바이너리 응답을 모두 처리하며, UI는 진행 상태·에러·다운로드 액션(열기/URL 복사)을 제공한다.
- 단계 5: 테이블 spec 분리는 완료됐고, 권한 경로 통일·Failure 파서 고도화·실패 메시지 통합·실제 API 플로우 검증이 잔여 과제로 남아 있다.
## 진행 현황 스냅샷 (2025-10-31 기준)
- 단계 1~2: 공통 네트워크 인프라와 마스터 도메인 원격 저장소/테스트가 모두 구축돼 실 API 계약 기준 코드가 자리잡았다.
- 단계 3: 결재 레이어는 저장소·컨트롤러·위젯 테스트까지 마쳤으며 `canProceed` API, `include_pending` 필터, 전표 타임스탬프 동기화 및 `FEATURE_APPROVAL_FLOW_V2` 토글 alias 대응이 반영됐다.
- 단계 4: 재고 트랜잭션 컨트롤러 submit/approve/reject/cancel/complete 전 구간을 API 호출로 전환고, Approval Draft 서버 저장/복원 사용자 흐름을 재고/결재 화면에 통합했다. 보고서 기능은 `ReportingRepositoryRemote` 바이너리/링크 응답을 모두 처리한다.
- 단계 5: 공통 테이블 사양과 Failure 매퍼 보강을 완료했고, 남은 작업은 배포 체크리스트(F8)와 스펙 회귀 테스트 확장이다.
- (2025-10-29) Approval Flow v2 대응을 위해 `ApprovalSubmissionInput` 등 도메인 입력 모델과 `/approval/submit|approve|reject|recall|resubmit|history` 호출을 Data 레이어에 도입했다. 기존 `create/update` 경로는 레거시 화면이 교체될 때까지 병행 유지한다.
- (2025-11-01) `ApprovalHistoryController`가 감사 로그·카탈로그 기반 코드→ID 캐싱(`_auditActions``_actionIdsByCode`)과 `ApiClient.buildQuery` `filters` 매개변수를 적용해 `approval_action_id`/`action_from`/`action_to`를 전송하며, 위젯 테스트(`approval_history_page_test.dart`)로 회귀를 감시한다.
## 문서 동기화 규칙
1. `superport_api_v2` 리포지터리의 `stock_approval_system_*.md` 문서를 단일 소스로 간주하고, 수정은 반드시 백엔드 리포지터리에서 먼저 수행한다.
2. 백엔드 문서 변경 후 프론트 리포지터리 루트에서 `tool/sync_stock_docs.sh`를 실행해 `doc/` 경로를 갱신한다. CI 또는 로컬 검증 시에는 `tool/sync_stock_docs.sh --check`로 차이를 확인한다.
3. 문서 차이가 감지되면 동기화 커밋을 생성하고 PR 본문에 백엔드 커밋 링크를 포함해 리뷰어가 출처를 추적할 수 있도록 한다.
## Approval Flow v2 연동 계획 (신규)
1. 백엔드 세부 작업 계획(`../superport_api_v2/doc/approval_flow_backend_task_plan.md`)과 프런트 작업 계획(`doc/approval_flow_frontend_task_plan.md`)을 기준으로 동시 진행한다.
2. 입고/출고/대여 등록 화면 결재 단계 구성 섹션을 추가하고 제출 요청에 Approval payload를 병합한다.
3. 결재 템플릿/이력 메뉴 `ShadTable` 기반으로 재구성하고 recall/resubmit, 감사 로그 UI를 확장한다.
4. Approval 관련 DTO/레포지리/유즈케이스를 전면 재정비하여 신규 엔드포인트(`/approval/submit|approve|reject|recall|resubmit`, `/approval/templates`)와 계약을 맞춘다.
5. 테스트 체계는 위젯/통합 테스트에서 결재 단계 추가/삭제/회수/재상신 플로우를 검증하고, `integration_test`에 시나리오를 추가한다.
## Approval Flow v2 연동 계획 (현황)
- [x] 백엔드 세부 작업 계획(`../superport_api_v2/doc/approval_flow_backend_task_plan.md`)과 프런트 작업 계획(`doc/approval_flow_frontend_task_plan.md`)을 동기화했다.
- [x] 입고/출고/대여 등록 화면 결재 단계 구성 섹션을 추가하고 제출 요청에 Approval payload를 병합했다 (`lib/features/inventory/*/presentation/pages/*_page.dart`).
- [x] 결재 템플릿/이력 메뉴 `ShadTable` 기반으로 재구성하고 recall/resubmit, 감사 로그 UI를 확장했다 (`lib/features/approvals/request/presentation/widgets/`, `lib/features/approvals/history/presentation/pages/approval_history_page.dart`).
- [x] Approval 관련 DTO/레포지리/유즈케이스를 전면 재정비하여 신규 엔드포인트(`/approval/submit|approve|reject|recall|resubmit`, `/approval/templates`)와 계약을 맞췄다 (`lib/features/approvals/data/repositories/approval_repository_remote.dart`, `lib/features/approvals/domain/usecases/*`).
- [x] 테스트 체계 결재 단계 추가/삭제/회수/재상신 위젯·통합 시나리오를 추가했고 `integration_test/approvals_flow_test.dart`로 회귀를 검증한다.
## 0. 사전 준비 및 브랜치 전략
1. 현재 백엔드 서버는 아직 기동되지 않았지만, 모든 기능은 실제 API 계약(`stock_approval_system_api_v4.md`)을 기준으로 구현한다.
1. 스테이징/운영 환경 여부와 무관하게 모든 기능은 실제 API 계약(`stock_approval_system_api_v4.md`)을 단일 소스로 삼고, 로컬 개발에서도 동일 계약을 기준으로 구현한다.
2. 프론트엔드 작업용 브랜치를 `feature/api-integration` 형태로 생성하고, 단계별 작업이 끝난 뒤 스쿼시 머지한다.
3. `.env.development`/`.env.production``API_BASE_URL`을 최신 서버 URL(가용 시)을 기입하고, 베이스 URL에는 버전 prefix(`/api/v1`)가 포함되지 않는다고 주석으로 명시한다.
@@ -128,11 +129,18 @@
- 최종 머지 전 `notify.py` 호출 및 릴리스 노트/환경 파일 확정 프로세스는 배포 승인 시점에 수행하도록 안내를 남긴다.
- 백엔드 v4 스펙 반영 체크리스트
- [ ] 재고 상태 전이 API 회귀 테스트를 `doc/stock_approval_system_api_v4.md` 4.7절 기준으로 재작성하고 submit/approve/reject/cancel/complete 호출 성공 여부를 통합 테스트에 반영한다.
- [ ] 그룹-메뉴 권한 복구 API(`POST /group-menu-permissions/{id}/restore`) 시나리오를 복구해 삭제/복구 UI가 `include_deleted=true` 응답을 사용하는지 검증한다.
- [ ] 백엔드 배포 확인 후 `FEATURE_STOCK_TRANSITIONS_ENABLED` 플래그 해제 시나리오와 운영 전환 체크리스트를 정리한다.
- [x] 재고 상태 전이 API 회귀 테스트를 `doc/stock_approval_system_api_v4.md` 4.7절 기준으로 재작성하고 submit/approve/reject/cancel/complete 호출 성공 여부를 통합 테스트에 반영한다.
- (2025-11-05) `integration_test/stock_transaction_state_flow_test.dart`에서 가상/실제 환경 모두 `submit → approve → complete`, `submit → cancel`, `submit → reject` 흐름을 검증하도록 리팩터링했다.
- 상태 전이 요청 본문에 `note` 필드를 전달하도록 `StockTransactionRepository` 인터페이스와 원격 구현·단위 테스트를 갱신했다.
- [x] 그룹-메뉴 권한 복구 API(`POST /group-menu-permissions/{id}/restore`) 시나리오를 복구해 삭제/복구 UI가 `include_deleted=true` 응답을 사용하는지 검증한다.
- 삭제 포함 토글이 활성화된 상태에서 복구 후 재조회 시 `include_deleted=true`가 유지되는지를 컨트롤러 단위 테스트로 심사하고, 복구 직후 목록이 최신 상태로 동기화되도록 확인했다.
- [x] 백엔드 배포 확인 후 `FEATURE_STOCK_TRANSITIONS_ENABLED` 플래그 해제 시나리오와 운영 전환 체크리스트를 정리한다.
- 운영 배포 전 점검: (1) 스테이징에서 통합 테스트 승인을 완료하고, (2) 백엔드 릴리스 노트의 마이그레이션 완료 여부를 확인한다.
- 배포 직후 절차: `assets/.env.production``FEATURE_STOCK_TRANSITIONS_ENABLED` 값을 `true`로 전환하고 운영 배포 파이프라인에서 해당 파일을 사용해 웹 번들을 재생성한다. 배포 이후 재고 화면의 상신/승인 버튼 노출과 토스트 메시지를 QA 체크리스트에 따라 검증한다.
- 롤백 가이드: 장애 발생 시 동일 순서로 토글을 `false`로 되돌리고, 통합 테스트의 `STAGING_RUN_TRANSACTION_FLOW``false`로 설정해 회귀 시나리오를 비활성화한다.
## 8. 재고 생성 결재 정보 수집 계획 (2024-08-XX 업데이트)
> 현황: `StockTransactionApprovalInput`과 인벤토리 컨트롤러 초안 저장 로직이 반영되어 UI/테스트 레벨에서 요구사항을 충족한다 (`lib/features/inventory/*/presentation/controllers/*_controller.dart`, `test/features/inventory/*/presentation/controllers/*_controller_test.dart`).
1. **신규 입력 필드 구성**
- 입고/출고/대여 등록 모달에 “결재 정보” 섹션을 추가하고 `거래번호`, `결재번호`, `결재 메모`, `결재 요청자` 필드를 배치한다.
- 거래번호는 수동 입력 + “번호 자동 생성” 버튼을 제공하고, 후자는 시퀀스 API(백엔드 지원 필요)와 연동한다.

View File

@@ -1,82 +1,65 @@
# 프런트엔드/백엔드 정합성 점검 리포트 (2025-10-21)
# 프런트엔드/백엔드 정합성 점검 리포트 (2025-10-23)
## 개요
- 기준 문서: `doc/backup/backend_change_requests.md`와 최신 계약 문서(`doc/stock_approval_system_api_v4.md`)를 토대로 Flutter 프런트(`superport_v2`)와 Rust 백엔드(`superport_api_v2`) 구현을 재검증했다.
- 백엔드 팀이 전달한 최신 패치(로그인/트랜잭션, 결재 단계, 대시보드·보고서, 권한)와 `cargo test` 통과 결과를 반영해 실제 로그인 → 대시보드 → 재고/결재 → 보고서/권한 흐름을 다시 점검했다.
- Approval Flow 전면 개편 합의를 위해 백엔드 작업 계획(`../superport_api_v2/doc/approval_flow_backend_task_plan.md`)과 프런트 작업 계획(`doc/approval_flow_frontend_task_plan.md`)을 신규 작성했다.
- 기준 문서: 갱신된 `backend_change_requests.md`(B8-2 완료)와 `stock_approval_system_api_v4.md`(Approval Flow v2 전면 개편 반영)를 토대로 Flutter 프런트(`superport_v2`)와 Rust 백엔드(`superport_api_v2`)의 계약을 재검증했다.
- 로그인 → 대시보드 → 재고/결재 → 보고서/권한까지 전 흐름을 재검증하고, 기본 목록 비노출 정책(B5-5) 적용 여부를 코드·테스트로 확인했다.
## 주요 정합성 결과
| 구분 | 내용 | 결과 | 후속 조치 |
| 구분 | 내용 | 상태 | 후속 조치 |
| --- | --- | --- | --- |
| 1 | 대여/출고 `expected_return_date` 저장·조회 | ✅ 해결 (`backend/src/domain/stock_transactions.rs:274`, `backend/src/adapters/repositories/stock_transactions.rs:808`) | 프런트 DTO·폼이 필드를 유지하는지 위젯 테스트로 확인 |
| 2 | 결재 단계 `q`·`status_id` 필터 및 `status` 응답 | ✅ 해결 (`backend/src/domain/approval_steps.rs:31`, `backend/src/adapters/repositories/approval_steps.rs:162`) | 검색/필터 UI와 리스트 표시가 새 계약(`status`)을 반영하는지 시나리오 테스트 필요 |
| 3 | 결재 단계 요청/응답 내 상태 필드 정규화 | ✅ 해결 (요청 `status_id`, 응답 `status`) | 프런트 DTO(`lib/features/approvals/step/domain/entities/approval_step_input.dart:30`)에서 레거시 `step_status_id` 제거 여부 확인 |
| 4 | 보고서 PDF 스트리밍·메타데이터 생성 | ✅ 해결 (`backend/src/api/v1/reports.rs:94`) | `ReportingRepositoryRemote`가 스트림·파일명 메타 처리하는지 합동 점검 |
| 5 | 그룹-메뉴 권한 `path`·`is_deleted`·`include_deleted` | ✅ 해결 (`backend/src/domain/group_menu_permissions.rs:149`, `backend/src/adapters/repositories/group_menu_permissions.rs:227`) | DTO/필터·권한 편집 UI가 추가 필드로 회귀 없는지 테스트 |
| 6 | 대시보드 KPI `delta` 전일 대비 비율 계산 | ✅ 해결 (`backend/src/adapters/repositories/dashboard.rs:61`) | KPI 카드/차트가 백분율·부호 표시를 지원하는지 확인 |
| 7 | 사용자 요약(`created_by`, `requester`) 기본 노출 및 회귀 테스트 | ✅ 해결 (`backend/src/domain/approval_templates.rs:34`, `backend/src/adapters/repositories/approval_templates.rs:100`, `backend/src/adapters/repositories/approvals.rs:878`, `backend/src/adapters/repositories/stock_transactions.rs:1173`, `backend/src/adapters/repositories/reports.rs:256`) | 프런트 DTO가 사번(`employee_id`)·이름을 모두 반영하는지, 리스트/리포트 표시가 정상인지 검증 |
| 8 | Approval Flow v2: 트랜잭션 결재 구성 필수화 + `/approval/*` 엔드포인트 확장 | 🚧 진행중 (`backend/src/domain/stock_transactions.rs:365`, `backend/src/domain/approvals/models.rs:583`, `backend/src/api/v1/approval_flow.rs:13`) | 프런트 DTO/리포지토리 확장(`lib/features/inventory/transactions/data/dtos/`, `lib/features/approvals/data/`) 및 기능 토글 기반 UI 연동 필요 |
| 1 | Approval Flow v2 API 문서 (`expected_updated_at`, drafts, 이력·지표) | ✅ 해결 | `tool/sync_stock_docs.sh`로 프런트 문서/DTO 재생성 |
| 2 | 결재/재고 응답 스키마(상태·메타데이터·중첩 객체) | ✅ 해결 | 프런트 DTO/위젯에 새 필드 반영 및 테스트 추가 |
| 3 | 전표 기본 목록 비노출 정책(B5-5) | ✅ 해결 | 기본 목록=승인·완료, 대기 영역은 `status=draft,submitted` 또는 `include_pending`으로 조회 |
| 4 | 보고서 Export(PDF/XLSX) 스트리밍·메타데이터 | ✅ 해결 | 감사 로그 확인 및 다운로드 UI 메타 필드 적용 |
| 5 | 그룹-메뉴 권한 `route_path`·`is_deleted`·`include_deleted` | ✅ 해결 | 편집 화면에 삭제 항목/경로 노출 및 회귀 테스트 |
| 6 | Prometheus 지표(`approval_flow_action_*`) 및 감사 로그 | ✅ 해결 | Ops 대시보드/알림 구성안 수립 |
아래 섹션에서 영역별 관찰 내용과 프런트엔드 후속 작업을 정리했다.
## 로그인 & 세션
- 변경 없음: 로그인/세션 API는 기존 계약과 동일하며(`backend/src/api/v1/login.rs`), 프런트 `AuthSessionDto` 매핑도 변동이 없다(`lib/features/auth/data/dtos/auth_session_dto.dart:17`).
- 체크포인트: 세션 만료 401 처리 시 백엔드 토큰 갱신 로직은 유지되므로, 프런트 재시도/로그아웃 UX를 QA 체크리스트에 유지한다.
- 추가 확인: `POST /api/v1/auth/refresh` 오류 메시지가 문서 규격(`token expired`, `token revoked`, `invalid token`)으로 일치하는지 스테이징 로그로 검증한다. 메시지 표준화가 미완료인 경우 `Failure` 매퍼에서 임시 매핑을 추가해야 한다.
- `backend/src/api/v1/auth.rs``data.access_token`, `data.refresh_token`, `data.expires_at`, `data.user`, `data.permissions`를 반환하며 오류 메시지는 문서 규격과 일치한다.
- 프런트 `AuthSessionDto` 매핑은 유지되지만, 알림 메시지가 영어 키(`invalid credentials`, `token expired` 등)에 맞춰 노출되는지 QA에서 다시 확인한다.
- 세션 만료 재로그인 UX는 기존대로 유지하되, 만료/재사용 토큰 구분 안내를 사용자에게 명확히 보여주는지 체크한다.
## 대시보드
- KPI `delta`가 전일 대비 증감률(예: `0.125` → 12.5%)로 채워지며(`backend/src/adapters/repositories/dashboard.rs:61`), 프런트는 % 포맷과 부호를 고려해 렌더링해야 한다(`lib/features/dashboard/presentation/widgets/dashboard_kpi_card.dart`).
- `step_summary` 포맷이 `"2단계 / 승인자"`에서 `"2단계 · 승인자"`로 정규화됐다. 문자열을 그대로 노출하는 UI라면 디자인팀과 표시 규칙을 다시 합의한다.
- 추가 활동: 대시보드 테스트에서 `delta != null` 기준으로 동작하는 메트릭 뱃지/차트 회귀 여부를 확인한다.
- `GET /api/v1/dashboard/summary``kpis[]`, `recent_transactions[]`, `pending_approvals[]`를 제공하고 `delta`·`trend_label`이 문서와 코드에 맞춰 채워진다(`backend/src/api/v1/dashboard.rs`).
- 프런트 KPI 카드에서 `delta`가 소수(0.125) → 백분율(12.5%)로 변환되는 로직과 `step_summary` 포맷(`"2단계 · 승인자"`)이 정상 노출되는지 UI 스냅샷 테스트를 업데이트한다.
## 재고·대여 트랜잭션
- `expected_return_date`가 생성/수정/조회 전 흐름에 포함된다(`backend/src/domain/stock_transactions.rs:274`, `backend/src/adapters/repositories/stock_transactions.rs:808`). 프런트 `StockTransactionInput``RentalPage`는 이미 필드를 전송하므로, 저장 후 상세/목록에서 값이 노출되는지 UI 테스트를 추가하면 된다(`lib/features/inventory/rental/presentation/pages/rental_page.dart:1651`).
- 마이그레이션 `migration/006_add_expected_return_date_to_stock_transactions.sql`을 반드시 적용해야 하며, 로컬/스테이징 DB에 컬럼이 없으면 500 에러가 발생한다. DevOps와 일정 합의 후 `diesel migration run`을 실행하고 `.env` DB URL을 재확인한다.
- 추가 확인: 고객 정보(`customers[].customer`), 거래 라인 메모, 템플릿명 등 선택 필드가 null일 때 키가 빠지지 않는지 샘플 데이터를 확보해 양쪽 DTO 직렬화/역직렬화 테스트를 보강한다.
- 최종 승인 완료 전에는 기본 입고/출고/대여 목록에서 전표가 숨겨져야 하므로, 프런트 목록/완료 카드에 `status=draft|submitted` 필터를 추가하고 대기 전용 섹션을 제공한다.
- 응답 본문이 거래/창고/라인/고객/결재 요약을 중첩 객체로 반환하고 `quantity`·`unit_price`의 null도 유지한다(`backend/src/api/v1/stock_transactions.rs`).
- 결재 전이 API가 `expected_updated_at`, `transaction_expected_updated_at`을 요구하며 최신 `data.transaction`/`data.approval`을 반환한다. 프런트는 낙관적 잠금 실패 시 메시지를 문서에 맞춰 노출해야 한다.
- 기본 목록은 승인·완료 상태만 반환하고, 초안·상신 전표는 `status=draft,submitted` 또는 `include_pending=true`로 별도 조회한다. (`backend/src/domain/stock_transactions.rs:74`, `backend/src/adapters/repositories/stock_transactions.rs:45`)
## 결재 단계
- 목록 API가 `q`·`status_id` 필터를 처리하고 응답에 `transaction_no`를 포함한다(`backend/src/adapters/repositories/approval_steps.rs:176`). 프런트 검색 바(`lib/features/approvals/step/presentation/controllers/approval_step_controller.dart`) 파라미터를 전달하는지, 리스트에서 거래번호를 표시하는지 확인한다.
- 도메인이 `status` 구조체(`{ id, name, code }`)를 반환한다(`backend/src/domain/approval_steps.rs:84`). 프런트 DTO는 `status_id` 입력과 `status` 응답을 모두 지원해야 하므로, 레거시 필드 제거와 단위 테스트(`test/features/approvals/step/domain/`) 성공 여부를 점검한다.
- 컨트롤러/위젯 테스트: 필터링, 상태 변경, 거래번호 표시 흐름을 추가해 회귀를 방지한다.
- 추가 확인: `histories[].action`에 레거시 데이터가 들어오는 경우(`id`, `name` 누락) 프런트가 안전하게 폴백 문자열을 표시하는지, 백엔드는 해당 케이스를 데이터 정제 로직으로 보완할지 정한다.
- 열람 권한은 상신자와 이미 결재한 승인자에게만 부여된다. 단계 미도달 승인자는 목록/상세 접근 시 403 처리되므로, 프런트 `ApprovalDetailPage`·`MyApprovals`에서 숨김/권한 안내 토스트를 구현하고 최종 승인 대기 상태에서도 상신자·중간 승인자만 접근 가능하도록 필터링한다.
## 결재 단계 & 행위
- `GET /api/v1/approval-steps``approver_id`, `approval_id`, `status_id`, `q` 필터와 `include=approval,approver,status` 확장을 지원한다. 프런트 컨트롤러 파라미터를 모두 전달하는지 점검한다.
- `/approval/**` 행위가 `expected_updated_at`을 요구하고 `data.approval`을 반환하며, Prometheus 지표(`approval_flow_action_total`, `approval_flow_action_duration_seconds`)가 발행된다.
- 열람 권한 정책이 상신자·기결재자에게만 상세 접근을 허용하므로, 프런트 `MyApprovals`·`ApprovalDetailPage`에서 403 시나리오 UX를 재확인한다.
## Approval Flow v2
- 입·출·대여 생성 요청에 `approval` 블록이 필수(`backend/src/domain/stock_transactions.rs:365`)이며, `approval.config`는 템플릿(`template_id`) 또는 직접 지정 단계(`steps[]`) 중 하나가 존재해야 한다(`backend/src/domain/stock_transactions.rs:384`). 프런트 입력모델(`lib/features/inventory/transactions/domain/entities/stock_transaction_input.dart:1`)은 `approval`을 선택이 아닌 필수 값으로 승격하고, 최소 1단계 + 최종 승인자 검증을 위젯 레벨에서 선반영해야 한다.
- 트랜잭션 목록 기본 필터는 승인 완료 건만 노출하고 `include_pending` 파라미터를 명시해야 대기/초안이 반환된다(`backend/src/domain/stock_transactions.rs:29`, `backend/src/domain/stock_transactions.rs:178`). 프런트 리스트 필터(`lib/features/inventory/transactions/domain/entities/stock_transaction_input.dart:158`)와 대시보드 카드가 새 파라미터를 전달하도록 조정하고, 결재 대기 전표 전용 섹션을 기능 토글(`feature.approval_flow_v2`)로 가드한다.
- 결재 제출·재상신 엔드포인트는 `ApprovalSubmitRequest`/`ApprovalResubmitRequest`를 사용하며 전 단계 배열을 전송해야 한다(`backend/src/domain/approvals/models.rs:583`, `backend/src/api/v1/approval_flow.rs:13`). 프런트는 `ApprovalRequestDto`·`ApprovalStepDto`·`ApprovalAuditDto` 신설 후 `ApprovalRepositoryRemote`를 통해 `/approval/submit|resubmit` 호출 시 단계 순번·승인자 ID를 직렬화한다.
- 승인/반려/회수 액션은 `actor_id`가 세션 사용자와 일치해야 하고 옵티미스틱 잠금(`expected_updated_at`, `transaction_expected_updated_at`)을 요구한다(`backend/src/domain/approvals/models.rs:624`, `backend/src/domain/approvals/models.rs:634`). 프런트 컨트롤러(`lib/features/approvals/presentation/controllers/approval_history_controller.dart`)는 서버 응답의 `approval.updated_at`을 저장해 재전송 시 파라미터로 포함해야 충돌 409를 피할 수 있다.
- 결재 상세 응답은 `current_step`, `steps[].status.is_blocking_next`, `histories[].action_code`를 포함하며 비허용 사용자에게는 `APPROVAL_ACCESS_DENIED`가 반환된다(`doc/stock_approval_system_api_v4.md:1011`, `backend/src/api/v1/approval_flow.rs:42`). DTO 파서(`lib/features/approvals/data/dtos/approval_dto.dart`)에서 새 서브 객체를 맵핑하고, 403 수신 시 접근 제한 안내를 표준 토스트로 노출한다.
- 프런트 데이터 계층에 `ApprovalSubmissionInput`/`ApprovalDecisionInput`/`ApprovalRecallInput`/`ApprovalResubmissionInput`을 추가하고, `ApprovalRepositoryRemote.submit|approve|reject|recall|resubmit|listHistory` 메서드를 신규 엔드포인트(`/approval/submit`, `/approval/approve`, `/approval/reject`, `/approval/recall`, `/approval/resubmit`, `/approval/history`)에 맞춰 구현했다. (`lib/features/approvals/domain/entities/approval.dart`, `lib/features/approvals/data/repositories/approval_repository_remote.dart`, 2025-10-29) — 기존 `create/update/assignSteps` 경로는 레거시 호환을 위해 유지하되, F2 단계에서 컨트롤러/유즈케이스를 새 흐름으로 전환할 예정이다.
## 결재 플로우 문서 & 모니터링
- `stock_approval_system_api_v4.md` `/approval`, `/approval-drafts`, 회수/재상신, 이력, 권한 정책, 예상 업데이트 시각(`expected_updated_at`)을 모두 포함한다.
- Prometheus 지표/Slack 알림 정책은 `doc/approval_flow_alert_policy.md`에 정리돼 있으니 Ops와 함께 대시보드 구성을 착수한다.
- 프런트 문서 동기화(`tool/sync_stock_docs.sh`)와 DTO 리프레시 후 회귀 테스트(`flutter analyze`, `flutter test`) 일정을 맞춘다.
## 보고서 (PDF)
- 백엔드가 PDF를 스트리밍으로 내려주고 파일명·Content-Length·ETag를 헤더에 포함한다(`backend/src/api/v1/reports.rs:94`). 프런트 `ReportingRepositoryRemote``StreamedResponse` 처리를 유지하되, 새 메타데이터(`report_name`, `generated_at`)로 다운로드 UI를 업데이트한다(`lib/features/reporting/presentation/controllers/reporting_controller.dart`).
- 단위 테스트(`backend/tests/api_reports_pdf.rs`)가 계약을 고정하고 있으므로, 프런트에서도 PDF 다운로드 및 실패 경로(404/500 등)를 위젯 테스트에 반영한다.
- 추가 확인: PDF 다운로드 요청이 감사 로그에 기록되는지 스테이징에서 확인하고, 정책상 필요 시 프런트 다운로드 성공/실패 토스트에 감사 로그 연동 여부를 표시한다.
## 보고서 (PDF/XLSX)
- `GET /api/v1/reports/transactions|approvals/export`가 스트리밍과 메타데이터 모드를 모두 지원하고, `download_url`, `filename`, `mime_type`, `expires_at`을 반환한다.
- 프런트 다운로드 UI는 새 메타 필드를 표시하고, 감사 로그(Download 요청 시 이벤트 남는지) 결과를 스테이징에서 확인한 뒤 UX 안내문구를 보강한다.
- 대용량 PDF/XLSX에 대한 합동 테스트를 QA 시나리오에 추가한다.
## 권한/문서
- 그룹-메뉴 권한 API가 `include_deleted=true` 시 삭제 항목을 함께 반환하고 각 항목에 `path`, `is_deleted`가 포함된다(`backend/src/domain/group_menu_permissions.rs:149`). 프런트 DTO(`lib/features/masters/group_permission/data/dtos/group_permission_dto.dart:49`)와 편집 UI가 새 필드를 사용하는지 확인한다.
- `doc/stock_approval_system_api_v4.md`가 갱신됐으므로, 프런트 문서는 `tool/sync_stock_docs.sh`로 재동기화한다.
- 추가 확인: 그룹-메뉴 배치 업데이트 API가 변경 이력을 남기는지 백엔드 로그로 점검하고, 프런트 편집 시 이력 누락에 대비한 사용자 안내를 준비한다.
- `GET /api/v1/group-menu-permissions``include_deleted`를 허용하고 `route_path`, `path`, `is_deleted`를 응답에 포함한다. 프런트 DTO와 편집 화면이 삭제 항목을 구분 표시하는지 확인한다.
- 백엔드/프런트 문서가 모두 최신 스펙을 참조하도록, `backend_change_requests.md`와 프런트 대응 문서를 동시에 업데이트한 후 공유한다.
## 공동 액션 아이템
| 구분 | 작업 내용 | 담당 | 상태 | 비고 |
| --- | --- | --- | --- | --- |
| DB | `006_add_expected_return_date_to_stock_transactions.sql` 적용 확인 | 백엔드 | 진행 예정 | 스테이징 DB 스키마 점검 후 공유 |
| 결재 | 단계 검색(`q`, `status_id`)·거래번호 노출 통합 테스트 | 프런트/백엔드 | 준비 | 계약 데이터 샘플 확보 필요 |
| 결재 | `histories.action` 레거시 데이터 폴백 처리 협의 | 프런트/백엔드 | 준비 | 데이터 정제 vs UI 폴백 선택 |
| 결재 | 열람 권한/대기 전표 노출 제한 구현 (`draft`,`submitted` 접근 제어) | 프런트/백엔드 | 준비 | API 403/필터 명세 동기화 |
| 보고서 | Approvals/Transactions PDF 스트리밍 합동 점검 | 프런트/백엔드 | 준비 | 대용량 파일·감사 로그 확인 |
| 보고서 | 감사 로그 정책 준수 여부 재확인 | 백엔드 | 준비 | 정책 준수 결과 문서화 |
| 결재 | Approval Flow 작업 계획 상호 공유(`doc/approval_flow_frontend_task_plan.md`) | 프런트/백엔드 | 완료 | 백엔드 문서(`../superport_api_v2/doc/approval_flow_backend_task_plan.md`)와 동기화 |
| QA | `flutter analyze`, `flutter test --coverage` 회귀 실행 후 공유 | 프런트 | 준비 | DTO/테스트 수정 후 `notify.py` 발송 |
| QA | `cargo test` + 통합 시나리오 스크립트 재실행 | 백엔드 | 준비 | 보고서/결재 단계 회귀 포함 |
## 일정 & 의존성
| 구분 | 작업 내용 | 담당 | 상태 | 목표일(제안) | 비고 |
| --- | --- | --- | --- | --- | --- |
| 결재 | 전표 기본 목록 비노출 정책 구현(B5-5) | 백엔드 | ✅ 완료 | 2025-10-24 | 기본 필터/테스트 반영 완료, 대기 목록은 상태 필터로 조회 |
| 결재 | 목록 비노출 UI/필터 적용 및 QA 시나리오 | 프런트 | 🗓️ 예정 | 2025-10-25 | 백엔드 배포 직후 테스트 연동 |
| 문서 | `tool/sync_stock_docs.sh` 실행 및 DTO 재생성 | 프런트 | 🗓️ 예정 | 2025-10-23 | 문서/코드 차이 검출 후 PR 공유 |
| QA | Approval Flow 통합 테스트 재실행 (`cargo test`) | 백엔드 | 🗓️ 예정 | 2025-10-24 | 보고서/결재 전이 회귀 포함 |
| QA | `flutter analyze`, `flutter test --coverage` | 프런트 | 🗓️ 예정 | 2025-10-26 | 새 DTO·UI 반영 후 보고 |
| Ops | Prometheus 지표 대시보드 구성 | 백엔드/Ops | 🗓️ 예정 | 2025-10-28 | `approval_flow_action_*` 메트릭 시각화 |
## 테스트 & 다음 단계
- Approval Flow 개선 과제는 `doc/approval_flow_frontend_task_plan.md`를 기준으로 우선순위를 재정정하고 백엔드 진행 상황과 주별 체크인을 맞춘다.
- 백엔드 `cargo test` 통과 보고가 공유됐지만, 프런트 QA 관점에서는 다음을 진행한다.
- 새 마이그레이션(`006_add_expected_return_date_to_stock_transactions.sql`) 적용 → 스테이징 DB 반영 상태 확인.
- 결재 단계 검색(`q`, `status_id`), 거래번호 노출, 결재 열람 권한 제한, 보고서 PDF 다운로드를 프런트/백엔드 합동 점검.
- `flutter analyze`, `flutter test --coverage`로 DTO·테스트 변경 이후 회귀 여부 확인.
- 모든 작업을 마치면 `notify.py` 워크플로를 통해 완료 알림을 발송한다.
- 백엔드는 기본 필터 동작 검증 후 `cargo fmt`, `cargo check`, `cargo test` 결과를 공유한다.
- 프런트는 문서/DTO 동기화 이후 Approval Flow UI·보고서 다운로드·권한 편집 시나리오를 회귀 테스트로 보강한다.
- 양측 모두 QA 완료 시 `notify.py` 워크플로로 완료 알림을 발송하고, 남은 일정(B8-4, B8-5, B9-x)을 공유 캘린더에 업데이트한다.

View File

@@ -0,0 +1,28 @@
# Approval Flow UAT 체크리스트
## 1. 환경 준비
- [ ] 스테이징 API `FEATURE_APPROVALS_ENABLED=true`, `FEATURE_STOCK_TRANSITIONS_ENABLED=true` 상태를 확인한다.
- [ ] 통합 테스트 토큰/식별자(`STAGING_*`)를 최신 값으로 교체하고 `flutter test integration_test/stock_transaction_state_flow_test.dart`를 드라이런한다.
- [ ] 결재 템플릿/승인자 마스터 데이터가 스테이징과 동기화되어 있는지 확인한다.
## 2. 핵심 플로우 검증
1. **입고 상신/승인**
- [ ] 입고 전표를 작성해 상신 → 승인 → 완료까지 진행하고 상태 변경/결재 이력을 확인한다.
2. **반려/재상신**
- [ ] 동일 전표를 반려 처리 후 요청자가 수정·재상신하여 승인까지 재진행한다.
3. **회수(Recall)**
- [ ] 승인 대기 상태에서 작성자가 회수한 뒤 수정 후 재상신 시 정상 동작하는지 확인한다.
4. **취소(Cancel)**
- [ ] 상신 직후 취소 시 상태가 초안(또는 취소)으로 복귀하고 결재 단계가 비워지는지 확인한다.
5. **템플릿 CRUD**
- [ ] 결재 템플릿 생성/수정/삭제/복구 후 전표에 적용되며 단계 구성·승인자 배정이 유지되는지 확인한다.
6. **대시보드/승인 목록 반영**
- [ ] 상신/승인/반려 이벤트 후 대시보드 및 결재 목록에 실시간으로 반영되는지 확인한다.
## 3. 예외/권한 시나리오
- [ ] 승인 대상이 아닌 사용자로 결재 목록/상세 조회 시 `APPROVAL_ACCESS_DENIED` 토스트와 대시보드 리다이렉트가 동작한다.
- [ ] 삭제된 결재 템플릿/전표를 복구했을 때 `include_deleted=true` 목록에서 재노출되는지 확인한다.
## 4. 보고 및 마무리
- [ ] 각 시나리오별 기대 결과/실측 결과를 QA 스프레드시트에 기록한다.
- [ ] 확인 완료 후 `doc/approval_flow_frontend_task_plan.md`의 F8-2 항목에 일자와 상태를 업데이트한다.

View File

@@ -1542,7 +1542,7 @@
- `POST /approvals/5001/restore`
### 5.9 결재 이력 조회
`GET /approval-histories?approval_id=5001&include=approval,step,approver`
`GET /approval-histories?approval_id=5001`
```json
{
"items": [
@@ -1607,6 +1607,8 @@
}
```
기본 응답에는 `approval`, `step`, `approval_action`, `approver`, `from_status`, `to_status` 서브 오브젝트가 포함되며, 추가 정보가 필요하지 않은 경우 `include` 파라미터를 생략해도 동일한 페이로드를 수신한다. `approval_action_id` 필터는 정수 ID 기준으로 동작하므로, 클라이언트는 사전에 제공된 행위 메타데이터로 코드 → ID 매핑을 수행한 뒤 요청해야 한다.
### 5.10 단계 개별 CRUD
- `GET /approval-steps?approval_id=5001&include=approval,approver,status``{ items: [], page, page_size, total }` 형태로 반환하며, 각 항목은 `approval`, `approver`, `status` 서브 오브젝트를 선택적으로 포함한다.
- `GET /approval-steps/7001?include=approval,approver,status``{ data: { ... } }`.
@@ -1618,10 +1620,11 @@
주요 필터 및 확장 파라미터:
- `approval_id`, `approval_step_id`, `approver_id`, `approval_action_id`(정수 ID), `status_id`
- `q`(결재번호·승인자 검색), `action_from`, `action_to` (ISO8601 UTC)
- `action_from`, `action_to` (ISO8601 UTC). 문자열 검색 파라미터 `q`는 2025-11-01 기준 제공되지 않으며, 도입 시 본 문서를 갱신한다.
- `sort=action_at|created_at|updated_at`, `order=asc|desc`
- `include` 기본값은 `approver,approval_action,from_status,to_status`; `approval`, `step`, `status` 토큰으로 확장
- `include` 기본값은 `approval,step,approval_action,approver,from_status,to_status`이며, `status` 토큰으로 응답을 확장할 수 있다.
- 응답은 `action` 오브젝트에 `name`/`code`를, 루트 레벨에 `action_code`를 포함하여 감사 행위 식별자를 일관되게 노출한다.
- 프런트엔드는 `approval_action_id` 정수 필터를 사용해야 하며, `approval_action.code`만으로는 필터링이 되지 않는다.
`GET /approval-histories/91001?include=approval,step`
```json
@@ -1680,6 +1683,78 @@
}
```
### 5.11 결재 초안 API (`/approval-drafts`)
- 상신자가 작성 중이던 결재 구성을 서버에 저장하고, 다른 세션에서 복구할 수 있도록 지원한다.
- 초안은 `requester_id`(상신자) 기준으로 구분되며 기본 목록은 유효(`status=active`) 초안만 반환한다. 만료된 초안을 함께 조회하려면 `include_expired=true`를 전달한다.
`GET /approval-drafts?requester_id=7`
```json
{
"items": [
{
"id": 88001,
"request_id": null,
"transaction_id": 91005,
"requester_id": 7,
"template_id": 1201,
"title": "입고 결재 초안",
"summary": "서류 미완료",
"status": "active",
"saved_at": "2025-01-04T05:10:00Z",
"expires_at": "2025-01-06T05:10:00Z",
"session_key": "draft-session-123",
"step_count": 2
}
],
"page": 1,
"page_size": 50,
"total": 1
}
```
`POST /approval-drafts`
```json
{
"requester_id": 7,
"transaction_id": 91005,
"template_id": 1201,
"title": "입고 결재 초안",
"summary": "서류 미완료",
"note": "재고 파악 필요",
"session_key": "draft-session-123",
"steps": [
{ "step_order": 1, "approver_id": 21, "is_optional": false },
{ "step_order": 2, "approver_id": 34, "is_optional": false }
]
}
```
`POST /approval-drafts/88001/restore`
```json
{
"data": {
"id": 88001,
"requester_id": 7,
"transaction_id": 91005,
"template_id": 1201,
"payload": {
"title": "입고 결재 초안",
"summary": "서류 미완료",
"note": "재고 파악 필요",
"status": "draft",
"steps": [
{ "step_order": 1, "approver_id": 21, "is_optional": false },
{ "step_order": 2, "approver_id": 34, "is_optional": false, "note": "재무 확인" }
]
},
"saved_at": "2025-01-04T05:10:00Z",
"expires_at": "2025-01-06T05:10:00Z",
"session_key": "draft-session-123"
}
}
```
- 초안 삭제는 `DELETE /approval-drafts/{id}?requester_id=<상신자 ID>`를 호출하며 `204 No Content`가 응답된다.
---
## 6. 결재 템플릿 API

View File

@@ -28,7 +28,7 @@
- OpenAPI 재생성(`backend/docs/openapi.generated.json`) 및 문서 싱크 확인.
## 단계별 작업 순서
1. **마이그레이션 작성 (`migration/010_migrate_employees_to_users.sql`)**
1. **마이그레이션 작성 (`migration/080_schema_migrate_employees_to_users.sql`)**
- 컬럼/테이블 rename, 새 컬럼 추가, 인덱스/트리거 재정의, 데이터 정규화.
2. **도메인/리포지토리/엔티티 리팩터링**
- `Employee*` 구조체 및 레포지토리를 `User*`로 일괄 교체.
@@ -45,7 +45,7 @@
- 통합 테스트 및 `cargo check`, `cargo fmt`, `cargo clippy`, `cargo test` 수행.
## 진행 현황 (2025-01-07)
- [x] `migration/010_migrate_employees_to_users.sql` 작성 및 컬럼/인덱스/트리거 갱신.
- [x] `migration/080_schema_migrate_employees_to_users.sql` 작성 및 컬럼/인덱스/트리거 갱신.
- [x] 도메인/레포지토리/인증 계층을 `users` 기준으로 리팩터링하고 비밀번호/사번 검증 로직 반영.
- [x] `/api/v1/users` + `/users/me` + `/users/{id}/reset-password` 등 사용자 API 구현 및 기존 `/employees` 제거.
- [x] 인증 토큰 강제 갱신 로직과 세션 무효화 훅 연동.
@@ -53,7 +53,7 @@
- [x] 문서(`stock_approval_system_api_v4.md`, `stock_approval_system_spec_v4.md`, alignment 보고서) 최종 검수.
## 중단 대비 메모
- `migration/010_migrate_employees_to_users.sql`이 적용된 상태이므로 롤백 시 `employees``users` rename 전후 스키마 차이를 반드시 확인할 것.
- `migration/080_schema_migrate_employees_to_users.sql`이 적용된 상태이므로 롤백 시 `employees``users` rename 전후 스키마 차이를 반드시 확인할 것.
- `/api/v1/users` 엔드포인트가 활성화되어 있으며, JWT `pwd_updated_at` 클레임 기반 세션 무효화가 도입되어 이전 토큰은 비밀번호 변경 직후 사용 불가하다.
- 승인/거래/리포트 모듈에서 사용자요약을 읽어가는 경로를 전수 점검 중이므로, 후속 담당자는 변경된 도메인 구조(`ApprovalUserSummary`, `StockTransactionUserSummary` 등)를 참고해 릴레이션 누락이 없는지 점검할 것.
- 리포트/승인/재고 레이어의 사용자 요약 회귀 테스트가 `backend/src/adapters/repositories/` 모듈에 추가돼 있으니 실패 시 최근 사용자 필드 변경 여부부터 확인한다.

View File

@@ -2,6 +2,14 @@
입고 등록/사용자 관리 기능에서 작성자(로그인 사용자) 정보를 정확히 추적하고, 관리자가 신규 사용자를 생성·관리할 수 있도록 백엔드/프런트엔드 동시 진행 항목을 정리했습니다. 기존 인증/세션 흐름과 충돌할 수 있으므로, 아래 항목을 참고해 단계별 검증을 병행하세요.
## 결재 플로우 용어 및 권한 매핑 (Approval Flow v2)
- **결재 요청자(Submitter)**: 트랜잭션 작성 시 자동으로 지정되는 사용자. 본인 단계에 대한 회수/재상신만 수행할 수 있으며, 결재 진행 현황을 전체 조회할 수 있다. (참조 문서: `doc/ApprovalFlow_System_Integration_and_ChangePlan.md`)
- **중간 승인자(Intermediate Approver)**: 순차 결재 단계에 배치되는 승인자. 지정된 단계에 도달했을 때만 승인/반려를 수행할 수 있으며, 중복 배치가 허용되지 않는다. (필요 권한: `approval.approve`)
- **최종 승인자(Final Approver)**: 결재 완료 여부를 확정하는 마지막 승인자. 결재 플로우 생성 시 반드시 포함되어야 하며, 승인 후 감사 로그가 기록된다. (필요 권한: `approval.approve`)
- **결재 관리자(Approval Manager)**: 결재 템플릿 생성/수정, 결재 요청 강제 종료 등 관리 기능을 실행하는 역할. 기능 토글(`feature.approval_flow_v2`) 활성화 시 백오피스에서 구성하며, 권한 키는 `approval.manage`로 통일한다.
- **감사 뷰어(Audit Viewer)**: 모든 결재 요청/이력에 대한 열람 권한을 가진 역할. 운영/QA용 계정에 부여하며, 권한 키는 `approval.view_all`을 사용한다.
- **슈퍼 관리자(Super Admin, terabits)**: 시스템 전역을 읽기 전용으로 조회할 수 있는 최고 권한. 결재 이력 수정은 금지되며, 실운영에서 데이터 정제 시 지원 역할로만 사용한다.
## 요구사항 요약
- 작성자는 현재 로그인한 사용자 계정을 사용한다. (기존 더미 계정 `terabits`는 최고 관리자 계정으로 유지하며 삭제하지 않는다)
- 신규 사용자는 관리자만 등록할 수 있으며, 필수 입력 필드는 `employee_id`, `name`, `phone`, `email`, `password`.
@@ -74,8 +82,8 @@
- 메일 발송 실패 시 재시도 큐를 3회까지 두고, 실패 알림을 Slack/Notify로 전송한다.
### 7. 테스트 & 배포 체크리스트
- [x] 사용자 생성, 자기 정보 수정, 관리자 초기화 API 통합 테스트 작성. (테스트: `test/features/masters/user/data/user_repository_remote_test.dart`)
- [x] 비밀번호 정책 유효성 테스트 (허용/거부 케이스) 구현. (테스트: `test/core/validation/password_rules_test.dart`)
- [ ] 사용자 생성, 자기 정보 수정, 관리자 초기화 API 통합 테스트 작성.
- [ ] 비밀번호 정책 유효성 테스트 (허용/거부 케이스) 구현.
- [ ] 마이그레이션 스크립트와 롤백 스크립트 준비.
- [ ] 배포 전 staging에서 실제 메일 발송 여부 검증.
- [ ] 기존 로그인 세션/토큰 구조와 충돌 여부 점검.
@@ -91,7 +99,6 @@
- 생성 성공 후 토스트 및 리스트 리프레시, 임시 비밀번호가 이메일로 발송됨을 명시한다.
- UI 구현 시 `lib/features/user_management/presentation/widgets/shad_user_table.dart``ShadTable` 컴포넌트를 기반으로 열 구성을 추가한다.
- 상태 관리는 `lib/features/user_management/presentation/controllers/user_controller.dart``createUser` 액션을 확장하고, 에러 핸들링을 중앙화한다.
- (2025-10-24) 신규 등록 모달에 임시 비밀번호/이메일/연락처 필수 검증을 적용하고 목록 액션에 비밀번호 재설정 버튼과 확인 다이얼로그를 추가했다. (`lib/features/masters/user/presentation/pages/user_page.dart`, 테스트: `test/features/masters/user/presentation/pages/user_page_test.dart`)
### 2. 관리자 > 사용자 상세 보기
- 비밀번호 재설정 버튼 추가: 클릭 시 확인 다이얼로그 → API 호출 → 성공 토스트.
@@ -106,7 +113,6 @@
- 비밀번호 변경 진입 버튼을 분리하고, 모달 또는 전용 페이지에서 3개 입력 필드를 제공한다.
- `lib/features/profile/presentation/pages/profile_page.dart`에서 폼 상태와 검증 로직을 `lib/features/profile/presentation/controllers/profile_controller.dart`로 분리한다.
- `lib/core/validation/password_rules.dart`에 비밀번호 정책 검증 유틸을 추가하고, 모든 비밀번호 입력 필드에서 재사용한다.
- (2025-10-24) 상단 내 정보 모달에 이메일/연락처 저장과 비밀번호 변경(강제 로그아웃) 흐름을 구현하고 테스트를 추가했다. (`lib/widgets/app_shell.dart`, 테스트: `test/widgets/app_shell_test.dart`)
### 4. 비밀번호 변경 플로우
- `현재 비밀번호`, `새 비밀번호`, `새 비밀번호 확인` 필드와 실시간 정책 검증(대/소문자, 숫자, 특수문자) UI를 구현한다.