feat(inventory): 재고 현황 요약/상세 플로우를 릴리스

- lib/features/inventory/summary 계층과 warehouse select 위젯을 추가해 목록/상세, 자동 새로고침, 필터, 상세 시트를 구현

- PermissionBootstrapper, scope 파서, 라우트 가드로 inventory.view 기반 권한 부여와 메뉴 노출을 통합(lib/core, lib/main.dart 등)

- Inventory Summary API/QA/Audit 문서와 PR 템플릿, CHANGELOG를 신규 스펙과 검증 커맨드로 업데이트

- DTO 직렬화 의존성을 추가하고 Golden·Widget·단위 테스트를 작성했으며 flutter analyze / flutter test --coverage를 통과
This commit is contained in:
JiWoong Sul
2025-11-09 01:13:02 +09:00
parent 486ab8706f
commit 47cc62a33d
72 changed files with 5453 additions and 1021 deletions

View File

@@ -82,3 +82,97 @@ return ApprovalDto.parsePaginated(response.data ?? const {});
- [x] 모든 Remote Repository가 ApiClient를 사용하도록 마이그레이션했다.
- [x] 에러/토큰/재시도 정책을 위젯 및 도메인 테스트에 연결했다.
- [x] 문서와 코드가 동기화되었으며, 변경 시 `tool/sync_stock_docs.sh --check`를 사용한다.
## 14) Inventory Summary API (신규)
### 14.1 목록 `GET /api/v1/inventory/summary`
- **권한**: `scope:inventory.view` + `menu_code=inventory` `can_read=true`
- **Query 파라미터**
- `page`, `page_size` (기본 1/50, 최대 200)
- `q`, `product_name`, `vendor_name`
- `warehouse_id`, `include_empty`, `updated_since`
- `sort`: `last_event_at|product_name|vendor_name|total_quantity`
- `order`: `asc|desc`
- **응답 스키마**
```json
{
"items": [
{
"product": {
"id": 101,
"product_code": "INV-DEMO-001",
"product_name": "QA 데모 장비",
"vendor": { "id": 10, "vendor_name": "Inventory Demo Vendor" }
},
"total_quantity": 145,
"warehouse_balances": [
{
"warehouse": {
"id": 1,
"warehouse_code": "INV-QA-A",
"warehouse_name": "QA 1센터"
},
"quantity": 115
}
],
"recent_event": {
"event_id": 15001,
"event_kind": "issue",
"event_label": "출고",
"delta_quantity": -20,
"counterparty": { "type": "customer", "name": "Inventory QA 고객" },
"warehouse": { "id": 1, "warehouse_code": "INV-QA-A", "warehouse_name": "QA 1센터" },
"transaction": { "id": 9100, "transaction_no": "INV-ISS-001" },
"occurred_at": "2025-10-24T02:58:00Z"
},
"updated_at": "2025-10-24T03:12:00Z"
}
],
"page": 1,
"page_size": 50,
"total": 1
}
```
- **오류**: `403 INVENTORY_SCOPE_REQUIRED`, `409 INVENTORY_SNAPSHOT_NOT_READY`
### 14.2 단건 `GET /api/v1/inventory/summary/{product_id}`
- **Query**: `event_limit`(1~100, 기본 20), `warehouse_id`
- **응답**
```json
{
"data": {
"product": { "id": 101, "product_code": "INV-DEMO-001", "product_name": "QA 데모 장비",
"vendor": { "id": 10, "vendor_name": "Inventory Demo Vendor" }
},
"total_quantity": 145,
"warehouse_balances": [
{
"warehouse": { "id": 1, "warehouse_code": "INV-QA-A", "warehouse_name": "QA 1센터" },
"quantity": 115
}
],
"recent_events": [
{
"event_id": 15001,
"event_kind": "issue",
"event_label": "출고",
"delta_quantity": -20,
"counterparty": { "type": "customer", "name": "Inventory QA 고객" },
"warehouse": { "id": 1, "warehouse_code": "INV-QA-A", "warehouse_name": "QA 1센터" },
"transaction": { "id": 9100, "transaction_no": "INV-ISS-001" },
"line": { "id": 12001, "line_no": 1, "quantity": 20 },
"occurred_at": "2025-10-24T02:58:00Z"
}
],
"updated_at": "2025-10-24T03:12:00Z",
"last_refreshed_at": "2025-10-24T03:10:00Z"
}
}
```
- **오류**: `403 INVENTORY_SCOPE_REQUIRED`, `409 INVENTORY_SNAPSHOT_NOT_READY`, `404 NOT_FOUND`
### 14.3 프런트 TODO
- DTO/JSON 직렬화: `InventorySummaryResponse`, `InventoryDetailResponse``build_runner` 재생성
- 상태관리: `InventorySummaryController`, `InventoryDetailController` (Pagination, 필터, `event_limit`)
- UI: 리스트(테이블) + 상세 시트, `warehouse_balances` 시각화, `recent_event` 배지
- 테스트: 위젯/Golden/통합 + `flutter analyze`, `flutter test --coverage`