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:
@@ -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`
|
||||
|
||||
Reference in New Issue
Block a user