환경 초기화 및 벤더 리포지토리 스켈레톤 도입
This commit is contained in:
955
doc/stock_approval_system_api_v4.md
Normal file
955
doc/stock_approval_system_api_v4.md
Normal file
@@ -0,0 +1,955 @@
|
||||
# 간단 입·출고 + 결재 시스템 API 규격 (v4)
|
||||
|
||||
**기준 버전:** 2025-09-18 16:22:30Z (UTC)
|
||||
|
||||
본 문서는 `stock_approval_system_spec_full_v4.md`의 데이터 모델과 비즈니스 규칙을 기반으로 한 REST API 구성을 정의한다. 기본 CRUD를 제공하며, 목록·상세 조회 시 FK로 연결된 주요 엔터티 정보를 함께 반환한다. 모든 엔드포인트는 소프트 삭제 컬럼(`is_deleted`)을 노출하지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 공통 규칙
|
||||
- **URI 규칙:** 복수형 리소스 명 사용. 기본 경로 예) `/api/v1/vendors`.
|
||||
- **표준 응답 구조:** 목록은 `{ items: [], page, page_size, total }`, 단건은 `{ data: { ... } }`.
|
||||
- **시간대:** 모든 날짜·시간은 ISO8601 UTC 문자열.
|
||||
- **소프트 삭제:** `DELETE /{res}/{id}` 호출 시 서버는 `is_deleted=true`, `is_active=false`로 처리하고 응답 바디는 `{ data: { id, deleted_at } }` 형식을 사용.
|
||||
- **복구:** `POST /{res}/{id}/restore`.
|
||||
- **공통 컬럼:** `note`, `is_active`, `created_at`, `updated_at`는 요청·응답에 필요 시 노출하되 `is_deleted`는 절대 노출하지 않는다.
|
||||
- **기본 필터:** 목록 조회 시 기본 쿼리 `active=true`, `deleted=false`. `deleted` 파라미터가 `true`일 때에만 삭제된 항목을 반환.
|
||||
- **증분 조회:** `updated_since=ISO8601`.
|
||||
- **정렬:** `sort`(기본 `updated_at`), `order=asc|desc`(기본 desc).
|
||||
- **검색:** `q` 파라미터로 코드/명칭 부분 일치. 필요한 경우 컬럼별 필터 지원.
|
||||
- **Include 확장:** `include` 쿼리로 추가 데이터(`lines`, `customers`, `approval`, `steps`, `histories`, `permissions`, `employees` 등) 선택 가능. 포함 대상은 FK 요약 정보를 이미 반환하므로 `include`는 상세 컬렉션을 불러올 때 사용.
|
||||
- **배열 입력:** 트랜잭션 라인, 트랜잭션 고객, 결재 단계, 그룹 메뉴 권한 등 다건 작업은 항상 배열(`[]`) 기반으로 요청한다.
|
||||
- **Primary Key 규칙:** Create 요청 바디에는 PK를 포함하지 않는다. Create 응답 및 나머지 모든 요청·응답에는 PK가 포함돼야 한다(경로에 이미 포함된 경우라도 바디 내 `id`를 명시).
|
||||
- **에러 규격:**
|
||||
- `400 BAD_REQUEST` — 검증 오류, 필수값 누락.
|
||||
- `404 NOT_FOUND` — 리소스 없음 또는 삭제됨.
|
||||
- `409 CONFLICT` — 유니크 제약, 결재 단계 상태 충돌.
|
||||
- `422 UNPROCESSABLE_ENTITY` — 비즈니스 규칙 위반(출고 고객 누락, blocking 상태 전이 등).
|
||||
- 에러 응답 예: `{ "error": { "code": 422, "message": "출고 트랜잭션에는 고객이 최소 1건 필요합니다.", "details": [...] } }`.
|
||||
|
||||
---
|
||||
|
||||
## 2. 타입(룩업) API
|
||||
대상: `/uoms`, `/transaction-types`, `/transaction-statuses`, `/approval-statuses`, `/approval-actions`
|
||||
|
||||
### 2.1 목록 조회
|
||||
`GET /{type}?page=1&page_size=50&active=true`
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "EA",
|
||||
"is_default": true,
|
||||
"is_active": true,
|
||||
"note": null,
|
||||
"created_at": "2025-01-01T00:00:00Z",
|
||||
"updated_at": "2025-02-01T03:00:00Z"
|
||||
}
|
||||
],
|
||||
"page": 1,
|
||||
"page_size": 50,
|
||||
"total": 1
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 단건 조회
|
||||
`GET /{type}/{id}`
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": 3,
|
||||
"name": "반려",
|
||||
"is_default": false,
|
||||
"is_blocking_next": true,
|
||||
"is_terminal": true,
|
||||
"is_active": true,
|
||||
"note": "최종 거절",
|
||||
"created_at": "2025-01-10T09:00:00Z",
|
||||
"updated_at": "2025-02-01T10:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 생성
|
||||
`POST /{type}`
|
||||
```json
|
||||
{
|
||||
"name": "진행중",
|
||||
"is_default": false,
|
||||
"is_blocking_next": true,
|
||||
"is_terminal": false,
|
||||
"is_active": true,
|
||||
"note": null
|
||||
}
|
||||
```
|
||||
응답:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": 4,
|
||||
"name": "진행중",
|
||||
"is_default": false,
|
||||
"is_blocking_next": true,
|
||||
"is_terminal": false,
|
||||
"is_active": true,
|
||||
"note": null,
|
||||
"created_at": "2025-03-01T00:00:00Z",
|
||||
"updated_at": "2025-03-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 수정
|
||||
`PATCH /{type}/{id}`
|
||||
```json
|
||||
{
|
||||
"id": 4,
|
||||
"is_blocking_next": false,
|
||||
"note": "임시 승인 허용"
|
||||
}
|
||||
```
|
||||
|
||||
### 2.5 삭제 & 복구
|
||||
- `DELETE /{type}/{id}` → `{ "data": { "id": 4, "deleted_at": "2025-03-05T09:00:00Z" } }`
|
||||
- `POST /{type}/{id}/restore` → `{ "data": { "id": 4, "restored_at": "2025-03-06T01:00:00Z" } }`
|
||||
|
||||
> `approval-statuses`는 추가 속성(`is_blocking_next`, `is_terminal`)을 사용하며, 다른 타입 테이블은 `name`, `is_default`, `is_active`, `note` 중심으로 작동한다.
|
||||
|
||||
---
|
||||
|
||||
## 3. 마스터 데이터 API
|
||||
리소스: `/vendors`, `/warehouses`, `/customers`, `/employees`, `/products`, `/menus`, `/groups`, `/zipcodes`
|
||||
|
||||
### 3.1 목록 조회
|
||||
`GET /vendors?page=1&q=한빛`
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": 10,
|
||||
"vendor_code": "V001",
|
||||
"vendor_name": "한빛상사",
|
||||
"note": "서울/경기 공급처",
|
||||
"is_active": true,
|
||||
"created_at": "2025-01-01T12:00:00Z",
|
||||
"updated_at": "2025-01-03T09:00:00Z"
|
||||
}
|
||||
],
|
||||
"page": 1,
|
||||
"page_size": 50,
|
||||
"total": 1
|
||||
}
|
||||
```
|
||||
|
||||
`GET /products?page=1&include=vendor`
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": 101,
|
||||
"product_code": "P100",
|
||||
"product_name": "샘플",
|
||||
"vendor": {
|
||||
"id": 10,
|
||||
"vendor_code": "V001",
|
||||
"vendor_name": "한빛상사"
|
||||
},
|
||||
"uom": {
|
||||
"id": 1,
|
||||
"uom_name": "EA",
|
||||
"is_default": true
|
||||
},
|
||||
"note": "출고 우선 재고",
|
||||
"is_active": true,
|
||||
"created_at": "2025-02-01T12:00:00Z",
|
||||
"updated_at": "2025-02-03T09:00:00Z"
|
||||
}
|
||||
],
|
||||
"page": 1,
|
||||
"page_size": 50,
|
||||
"total": 1
|
||||
}
|
||||
```
|
||||
|
||||
`GET /warehouses?page=1`
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": 20,
|
||||
"warehouse_code": "WH-001",
|
||||
"warehouse_name": "1센터",
|
||||
"zipcode": {
|
||||
"zipcode": "06000",
|
||||
"sido": "서울특별시",
|
||||
"sigungu": "강남구",
|
||||
"road_name": "테헤란로"
|
||||
},
|
||||
"address_detail": "강남파이낸스센터 10층",
|
||||
"is_active": true,
|
||||
"created_at": "2025-01-05T08:00:00Z",
|
||||
"updated_at": "2025-01-10T09:30:00Z"
|
||||
}
|
||||
],
|
||||
"page": 1,
|
||||
"page_size": 50,
|
||||
"total": 1
|
||||
}
|
||||
```
|
||||
|
||||
`GET /customers?page=1`
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": 301,
|
||||
"customer_code": "C001",
|
||||
"customer_name": "ABC물류",
|
||||
"is_partner": true,
|
||||
"is_general": false,
|
||||
"email": "contact@abc.com",
|
||||
"mobile_no": "010-1234-5678",
|
||||
"zipcode": {
|
||||
"zipcode": "06000",
|
||||
"sido": "서울특별시",
|
||||
"sigungu": "강남구",
|
||||
"road_name": "테헤란로"
|
||||
},
|
||||
"address_detail": "10층",
|
||||
"note": "VIP 고객",
|
||||
"is_active": true,
|
||||
"created_at": "2025-01-15T11:00:00Z",
|
||||
"updated_at": "2025-01-20T08:10:00Z"
|
||||
}
|
||||
],
|
||||
"page": 1,
|
||||
"page_size": 50,
|
||||
"total": 1
|
||||
}
|
||||
```
|
||||
|
||||
`GET /employees?page=1`
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": 7,
|
||||
"employee_no": "E2025001",
|
||||
"employee_name": "김승인",
|
||||
"email": "approver@example.com",
|
||||
"mobile_no": "010-2222-1111",
|
||||
"group": {
|
||||
"id": 2,
|
||||
"group_name": "창고 관리자"
|
||||
},
|
||||
"is_active": true,
|
||||
"created_at": "2025-01-02T09:00:00Z",
|
||||
"updated_at": "2025-01-10T11:00:00Z"
|
||||
}
|
||||
],
|
||||
"page": 1,
|
||||
"page_size": 50,
|
||||
"total": 1
|
||||
}
|
||||
```
|
||||
|
||||
`GET /groups?include=permissions,employees`
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": 2,
|
||||
"group_name": "창고 관리자",
|
||||
"group_description": "창고 및 재고 관리",
|
||||
"is_default": false,
|
||||
"is_active": true,
|
||||
"permissions": [
|
||||
{
|
||||
"id": 8,
|
||||
"menu": {
|
||||
"id": 12,
|
||||
"menu_code": "STOCK_MGMT",
|
||||
"menu_name": "입출고 관리"
|
||||
},
|
||||
"can_create": true,
|
||||
"can_read": true,
|
||||
"can_update": true,
|
||||
"can_delete": false
|
||||
}
|
||||
],
|
||||
"employees": [
|
||||
{
|
||||
"id": 7,
|
||||
"employee_no": "E2025001",
|
||||
"employee_name": "김승인"
|
||||
}
|
||||
],
|
||||
"created_at": "2025-01-01T00:00:00Z",
|
||||
"updated_at": "2025-01-15T00:00:00Z"
|
||||
}
|
||||
],
|
||||
"page": 1,
|
||||
"page_size": 50,
|
||||
"total": 1
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 단건 조회
|
||||
`GET /products/101`
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": 101,
|
||||
"product_code": "P100",
|
||||
"product_name": "샘플",
|
||||
"vendor": {
|
||||
"id": 10,
|
||||
"vendor_code": "V001",
|
||||
"vendor_name": "한빛상사",
|
||||
"note": "서울/경기 공급처"
|
||||
},
|
||||
"uom": {
|
||||
"id": 1,
|
||||
"uom_name": "EA",
|
||||
"is_default": true
|
||||
},
|
||||
"note": "출고 우선 재고",
|
||||
"is_active": true,
|
||||
"created_at": "2025-02-01T12:00:00Z",
|
||||
"updated_at": "2025-02-03T09:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 생성
|
||||
`POST /vendors`
|
||||
```json
|
||||
{
|
||||
"vendor_code": "V002",
|
||||
"vendor_name": "미래상사",
|
||||
"note": "부산 공급처",
|
||||
"is_active": true
|
||||
}
|
||||
```
|
||||
응답:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": 11,
|
||||
"vendor_code": "V002",
|
||||
"vendor_name": "미래상사",
|
||||
"note": "부산 공급처",
|
||||
"is_active": true,
|
||||
"created_at": "2025-03-01T00:00:00Z",
|
||||
"updated_at": "2025-03-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 수정
|
||||
`PATCH /products/101`
|
||||
```json
|
||||
{
|
||||
"id": 101,
|
||||
"product_name": "샘플 A",
|
||||
"note": "재고 우선순위 변경"
|
||||
}
|
||||
```
|
||||
|
||||
### 3.5 삭제 & 복구
|
||||
- `DELETE /products/101`
|
||||
- `POST /products/101/restore`
|
||||
|
||||
### 3.6 그룹 메뉴 권한 일괄 갱신
|
||||
`POST /groups/2/permissions`
|
||||
```json
|
||||
{
|
||||
"id": 2,
|
||||
"entries": [
|
||||
{
|
||||
"menu_id": 12,
|
||||
"can_create": true,
|
||||
"can_read": true,
|
||||
"can_update": true,
|
||||
"can_delete": false
|
||||
},
|
||||
{
|
||||
"menu_id": 13,
|
||||
"can_create": false,
|
||||
"can_read": true,
|
||||
"can_update": false,
|
||||
"can_delete": false
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
응답은 갱신된 권한 목록을 반환.
|
||||
|
||||
> `zipcodes`는 대량 데이터 특성상 `GET /zipcodes?zipcode=06000&road_name=세종대로` 형태로 조회하며, 응답 항목에는 `zipcode`, `sido`, `sigungu`, `road_name`, `building_main_no` 등 주소 구성 요소가 포함된다.
|
||||
|
||||
---
|
||||
|
||||
## 4. 트랜잭션 API
|
||||
리소스: `/stock-transactions`, 보조 리소스: `/transaction-lines`, `/transaction-customers`
|
||||
|
||||
### 4.1 생성 (헤더 + 라인 + 고객 다건)
|
||||
`POST /stock-transactions`
|
||||
```json
|
||||
{
|
||||
"transaction_no": "TXN-2025-0001",
|
||||
"transaction_type_id": 1,
|
||||
"transaction_status_id": 1,
|
||||
"warehouse_id": 1,
|
||||
"transaction_date": "2025-09-18",
|
||||
"created_by_id": 7,
|
||||
"note": "창고 입고",
|
||||
"lines": [
|
||||
{
|
||||
"line_no": 1,
|
||||
"product_id": 101,
|
||||
"quantity": 50,
|
||||
"unit_price": 1200
|
||||
},
|
||||
{
|
||||
"line_no": 2,
|
||||
"product_id": 102,
|
||||
"quantity": 20,
|
||||
"unit_price": 0
|
||||
}
|
||||
],
|
||||
"customers": []
|
||||
}
|
||||
```
|
||||
응답은 생성된 트랜잭션 전체 정보를 반환하며, 라인·고객 식별자가 포함된다.
|
||||
|
||||
### 4.2 목록 조회
|
||||
`GET /stock-transactions?include=lines,customers,approval`
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": 9001,
|
||||
"transaction_no": "TXN-2025-0001",
|
||||
"transaction_type": {
|
||||
"id": 1,
|
||||
"type_name": "입고"
|
||||
},
|
||||
"transaction_status": {
|
||||
"id": 1,
|
||||
"status_name": "초안"
|
||||
},
|
||||
"warehouse": {
|
||||
"id": 1,
|
||||
"warehouse_code": "WH-001",
|
||||
"warehouse_name": "1센터"
|
||||
},
|
||||
"transaction_date": "2025-09-18",
|
||||
"created_by": {
|
||||
"id": 7,
|
||||
"employee_no": "E2025001",
|
||||
"employee_name": "김승인"
|
||||
},
|
||||
"note": "창고 입고",
|
||||
"is_active": true,
|
||||
"created_at": "2025-09-18T05:00:00Z",
|
||||
"updated_at": "2025-09-18T05:00:00Z",
|
||||
"lines": [
|
||||
{
|
||||
"id": 12001,
|
||||
"line_no": 1,
|
||||
"product": {
|
||||
"id": 101,
|
||||
"product_code": "P100",
|
||||
"product_name": "샘플",
|
||||
"vendor": {
|
||||
"id": 10,
|
||||
"vendor_name": "한빛상사"
|
||||
},
|
||||
"uom": {
|
||||
"id": 1,
|
||||
"uom_name": "EA"
|
||||
}
|
||||
},
|
||||
"quantity": 50,
|
||||
"unit_price": 1200,
|
||||
"note": null
|
||||
}
|
||||
],
|
||||
"customers": [],
|
||||
"approval": null
|
||||
}
|
||||
],
|
||||
"page": 1,
|
||||
"page_size": 50,
|
||||
"total": 1
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 단건 조회
|
||||
`GET /stock-transactions/9001?include=lines,customers,approval,approval.steps`
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": 9001,
|
||||
"transaction_no": "TXN-2025-0001",
|
||||
"transaction_type": {
|
||||
"id": 1,
|
||||
"type_name": "입고"
|
||||
},
|
||||
"transaction_status": {
|
||||
"id": 1,
|
||||
"status_name": "초안"
|
||||
},
|
||||
"warehouse": {
|
||||
"id": 1,
|
||||
"warehouse_code": "WH-001",
|
||||
"warehouse_name": "1센터",
|
||||
"zipcode": {
|
||||
"zipcode": "06000",
|
||||
"sido": "서울특별시"
|
||||
}
|
||||
},
|
||||
"transaction_date": "2025-09-18",
|
||||
"created_by": {
|
||||
"id": 7,
|
||||
"employee_no": "E2025001",
|
||||
"employee_name": "김승인"
|
||||
},
|
||||
"note": "창고 입고",
|
||||
"is_active": true,
|
||||
"created_at": "2025-09-18T05:00:00Z",
|
||||
"updated_at": "2025-09-18T05:00:00Z",
|
||||
"lines": [
|
||||
{
|
||||
"id": 12001,
|
||||
"line_no": 1,
|
||||
"product": {
|
||||
"id": 101,
|
||||
"product_code": "P100",
|
||||
"product_name": "샘플",
|
||||
"vendor": {
|
||||
"id": 10,
|
||||
"vendor_name": "한빛상사"
|
||||
},
|
||||
"uom": {
|
||||
"id": 1,
|
||||
"uom_name": "EA"
|
||||
}
|
||||
},
|
||||
"quantity": 50,
|
||||
"unit_price": 1200,
|
||||
"note": null
|
||||
}
|
||||
],
|
||||
"customers": [],
|
||||
"approval": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 헤더 수정
|
||||
`PATCH /stock-transactions/9001`
|
||||
```json
|
||||
{
|
||||
"id": 9001,
|
||||
"transaction_status_id": 2,
|
||||
"note": "상신 준비"
|
||||
}
|
||||
```
|
||||
|
||||
### 4.5 라인 다건 추가/수정/삭제
|
||||
- **추가:** `POST /stock-transactions/9001/lines`
|
||||
```json
|
||||
{
|
||||
"id": 9001,
|
||||
"lines": [
|
||||
{
|
||||
"line_no": 2,
|
||||
"product_id": 102,
|
||||
"quantity": 20,
|
||||
"unit_price": 900
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- **일괄 수정:** `PATCH /stock-transactions/9001/lines`
|
||||
```json
|
||||
{
|
||||
"id": 9001,
|
||||
"lines": [
|
||||
{
|
||||
"id": 12001,
|
||||
"line_no": 1,
|
||||
"quantity": 60,
|
||||
"note": "추가 입고"
|
||||
},
|
||||
{
|
||||
"id": 12002,
|
||||
"line_no": 2,
|
||||
"unit_price": 950
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- **삭제:** `DELETE /transaction-lines/12002`
|
||||
- **복구:** `POST /transaction-lines/12002/restore`
|
||||
|
||||
### 4.6 고객 연결 다건 관리
|
||||
- **추가:** `POST /stock-transactions/9100/customers`
|
||||
```json
|
||||
{
|
||||
"id": 9100,
|
||||
"customers": [
|
||||
{
|
||||
"customer_id": 301,
|
||||
"note": "1차 납품"
|
||||
},
|
||||
{
|
||||
"customer_id": 302,
|
||||
"note": "2차 납품"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- **수정:** `PATCH /stock-transactions/9100/customers`
|
||||
```json
|
||||
{
|
||||
"id": 9100,
|
||||
"customers": [
|
||||
{
|
||||
"id": 33001,
|
||||
"note": "수량 조정"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- **삭제:** `DELETE /transaction-customers/33001`
|
||||
|
||||
### 4.7 상태 전이 권장 API
|
||||
- `POST /stock-transactions/9001/submit`
|
||||
- `POST /stock-transactions/9001/complete`
|
||||
|
||||
응답은 `{ "data": { "id": 9001, "transaction_status": { ... }, "updated_at": "..." } }` 형태.
|
||||
|
||||
---
|
||||
|
||||
## 5. 결재 API
|
||||
리소스: `/approvals`, 보조 리소스: `/approval-steps`, `/approval-histories`
|
||||
|
||||
### 5.1 결재 생성
|
||||
`POST /approvals`
|
||||
```json
|
||||
{
|
||||
"transaction_id": 9001,
|
||||
"approval_no": "APP-2025-0001",
|
||||
"approval_status_id": 1,
|
||||
"requested_by_id": 7,
|
||||
"note": "입고 결재"
|
||||
}
|
||||
```
|
||||
응답에는 `id`와 현재 단계 정보가 포함된다.
|
||||
|
||||
### 5.2 목록 조회
|
||||
`GET /approvals?include=steps,histories`
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": 5001,
|
||||
"approval_no": "APP-2025-0001",
|
||||
"transaction": {
|
||||
"id": 9001,
|
||||
"transaction_no": "TXN-2025-0001"
|
||||
},
|
||||
"approval_status": {
|
||||
"id": 1,
|
||||
"status_name": "대기",
|
||||
"is_blocking_next": true
|
||||
},
|
||||
"current_step": null,
|
||||
"requested_by": {
|
||||
"id": 7,
|
||||
"employee_no": "E2025001",
|
||||
"employee_name": "김승인"
|
||||
},
|
||||
"requested_at": "2025-09-18T06:00:00Z",
|
||||
"decided_at": null,
|
||||
"note": "입고 결재",
|
||||
"is_active": true,
|
||||
"created_at": "2025-09-18T06:00:00Z",
|
||||
"updated_at": "2025-09-18T06:00:00Z",
|
||||
"steps": [],
|
||||
"histories": []
|
||||
}
|
||||
],
|
||||
"page": 1,
|
||||
"page_size": 50,
|
||||
"total": 1
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 단건 조회
|
||||
`GET /approvals/5001?include=steps,histories`
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": 5001,
|
||||
"approval_no": "APP-2025-0001",
|
||||
"transaction": {
|
||||
"id": 9001,
|
||||
"transaction_no": "TXN-2025-0001"
|
||||
},
|
||||
"approval_status": {
|
||||
"id": 1,
|
||||
"status_name": "대기",
|
||||
"is_blocking_next": true,
|
||||
"is_terminal": false
|
||||
},
|
||||
"current_step": null,
|
||||
"requested_by": {
|
||||
"id": 7,
|
||||
"employee_no": "E2025001",
|
||||
"employee_name": "김승인"
|
||||
},
|
||||
"requested_at": "2025-09-18T06:00:00Z",
|
||||
"decided_at": null,
|
||||
"note": "입고 결재",
|
||||
"steps": [
|
||||
{
|
||||
"id": 7001,
|
||||
"step_order": 1,
|
||||
"approver": {
|
||||
"id": 21,
|
||||
"employee_no": "E2025002",
|
||||
"employee_name": "박검토"
|
||||
},
|
||||
"step_status": {
|
||||
"id": 1,
|
||||
"status_name": "대기",
|
||||
"is_blocking_next": true
|
||||
},
|
||||
"assigned_at": "2025-09-18T06:05:00Z",
|
||||
"decided_at": null,
|
||||
"note": null
|
||||
}
|
||||
],
|
||||
"histories": [],
|
||||
"created_at": "2025-09-18T06:00:00Z",
|
||||
"updated_at": "2025-09-18T06:05:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 단계 구성 (배치 생성)
|
||||
`POST /approvals/5001/steps`
|
||||
```json
|
||||
{
|
||||
"id": 5001,
|
||||
"steps": [
|
||||
{
|
||||
"step_order": 1,
|
||||
"approver_id": 21,
|
||||
"note": null
|
||||
},
|
||||
{
|
||||
"step_order": 2,
|
||||
"approver_id": 34,
|
||||
"note": "재무 확인"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 5.5 단계 일괄 수정/재배치
|
||||
`PATCH /approvals/5001/steps`
|
||||
```json
|
||||
{
|
||||
"id": 5001,
|
||||
"steps": [
|
||||
{
|
||||
"id": 7001,
|
||||
"step_order": 1,
|
||||
"note": "서류 확인 중"
|
||||
},
|
||||
{
|
||||
"id": 7002,
|
||||
"step_order": 2,
|
||||
"approver_id": 35
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 5.6 단계 행위
|
||||
`POST /approval-steps/7001/actions`
|
||||
```json
|
||||
{
|
||||
"id": 7001,
|
||||
"approval_action_id": 1,
|
||||
"note": "승인합니다."
|
||||
}
|
||||
```
|
||||
응답에는 전후 상태(`from_status`, `to_status`), 차기 단계 정보가 포함되며, `approval_histories`에 기록된다.
|
||||
|
||||
### 5.7 결재 상태 확인
|
||||
`GET /approvals/5001/can-proceed`
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": 5001,
|
||||
"can_proceed": true,
|
||||
"reason": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.8 결재 수정·삭제·복구
|
||||
- `PATCH /approvals/5001`
|
||||
```json
|
||||
{
|
||||
"id": 5001,
|
||||
"approval_status_id": 2,
|
||||
"note": "보류 처리"
|
||||
}
|
||||
```
|
||||
- `DELETE /approvals/5001`
|
||||
- `POST /approvals/5001/restore`
|
||||
|
||||
---
|
||||
|
||||
## 6. 결재 템플릿 API
|
||||
리소스: `/approval-templates`
|
||||
|
||||
### 6.1 목록 조회
|
||||
`GET /approval-templates?page=1`
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": 3001,
|
||||
"template_code": "AP_INBOUND",
|
||||
"template_name": "입고 결재 기본",
|
||||
"description": "입고 결재 2단계",
|
||||
"created_by": {
|
||||
"id": 7,
|
||||
"employee_no": "E2025001",
|
||||
"employee_name": "김승인"
|
||||
},
|
||||
"is_active": true,
|
||||
"created_at": "2025-01-20T00:00:00Z",
|
||||
"updated_at": "2025-01-25T00:00:00Z"
|
||||
}
|
||||
],
|
||||
"page": 1,
|
||||
"page_size": 50,
|
||||
"total": 1
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 단건 조회
|
||||
`GET /approval-templates/3001?include=steps`
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": 3001,
|
||||
"template_code": "AP_INBOUND",
|
||||
"template_name": "입고 결재 기본",
|
||||
"description": "입고 결재 2단계",
|
||||
"created_by": {
|
||||
"id": 7,
|
||||
"employee_no": "E2025001",
|
||||
"employee_name": "김승인"
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"id": 9101,
|
||||
"step_order": 1,
|
||||
"approver": {
|
||||
"id": 21,
|
||||
"employee_no": "E2025002",
|
||||
"employee_name": "박검토"
|
||||
},
|
||||
"note": null
|
||||
}
|
||||
],
|
||||
"is_active": true,
|
||||
"created_at": "2025-01-20T00:00:00Z",
|
||||
"updated_at": "2025-01-25T00:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 생성·수정
|
||||
- `POST /approval-templates`
|
||||
```json
|
||||
{
|
||||
"template_code": "AP_OUTBOUND",
|
||||
"template_name": "출고 결재 기본",
|
||||
"description": "출고 결재 3단계",
|
||||
"created_by_id": 7,
|
||||
"note": "표준 출고"
|
||||
}
|
||||
```
|
||||
|
||||
- `POST /approval-templates/3002/steps`
|
||||
```json
|
||||
{
|
||||
"id": 3002,
|
||||
"steps": [
|
||||
{
|
||||
"step_order": 1,
|
||||
"approver_id": 34
|
||||
},
|
||||
{
|
||||
"step_order": 2,
|
||||
"approver_id": 55
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- `PATCH /approval-templates/3002`
|
||||
```json
|
||||
{
|
||||
"id": 3002,
|
||||
"template_name": "출고 결재 확장",
|
||||
"note": "정기 출고용"
|
||||
}
|
||||
```
|
||||
|
||||
- `PATCH /approval-templates/3002/steps`
|
||||
```json
|
||||
{
|
||||
"id": 3002,
|
||||
"steps": [
|
||||
{
|
||||
"id": 9105,
|
||||
"step_order": 1,
|
||||
"approver_id": 36
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- 삭제/복구: `DELETE /approval-templates/{id}`, `POST /approval-templates/{id}/restore`
|
||||
|
||||
---
|
||||
|
||||
## 7. 보고서 API (선택)
|
||||
- `GET /reports/transactions/export?from=2025-09-01&to=2025-09-30&type_id=2&warehouse_id=1&format=xlsx`
|
||||
- `GET /reports/approvals/export?status_id=1&format=pdf`
|
||||
|
||||
응답은 파일 다운로드 링크 또는 스트림. 요청 파라미터에는 대상 리소스의 PK를 포함한다.
|
||||
|
||||
---
|
||||
|
||||
## 8. 구현 참고
|
||||
- FK 요약 정보는 기본 응답에 포함하며, 상세 정보가 필요하면 `include` 파라미터를 활용해 확장한다.
|
||||
- 배열 기반 다건 작업은 전체를 트랜잭션 처리해야 한다. 실패 시 롤백하고 부분 처리 결과를 반환하지 않는다.
|
||||
- `is_active` 변경은 권한·결재 등의 즉시성 요구를 고려하여 관련 캐시를 무효화한다.
|
||||
- 결재 단계 상태 전이는 `approval_statuses.is_blocking_next` 규칙을 준수해야 하며, 반려(`is_terminal=true`) 상태 시 결재를 종료한다.
|
||||
Reference in New Issue
Block a user