# 단가관리 API 분석 및 구현 현황 > **작성일**: 2025-12-08 > **최종 업데이트**: 2025-12-08 > **상태**: ✅ **백엔드 API 구현 완료** --- ## 1. 구현 현황 요약 ### ✅ 현재 API 구조 (구현 완료) | Method | Endpoint | 설명 | 상태 | |--------|----------|------|------| | `GET` | `/api/v1/pricing` | 목록 조회 (페이지네이션, 필터) | ✅ 완료 | | `GET` | `/api/v1/pricing/{id}` | 단건 조회 | ✅ 완료 | | `POST` | `/api/v1/pricing` | 등록 | ✅ 완료 | | `PUT` | `/api/v1/pricing/{id}` | 수정 | ✅ 완료 | | `DELETE` | `/api/v1/pricing/{id}` | 삭제 (soft delete) | ✅ 완료 | | `POST` | `/api/v1/pricing/{id}/finalize` | 확정 | ✅ 완료 | | `GET` | `/api/v1/pricing/{id}/revisions` | 변경이력 조회 | ✅ 완료 | | `POST` | `/api/v1/pricing/by-items` | 품목별 단가 현황 (다건) | ✅ 완료 | | `GET` | `/api/v1/pricing/cost` | 원가 조회 (수입검사 > 표준원가) | ✅ 완료 | ### ✅ 완료된 사항 - **새 테이블 구조**: `prices`, `price_revisions` 테이블 생성 - **기존 테이블 마이그레이션**: `price_histories` → `prices` 데이터 이관 - **모든 요청 필드**: 원가 계산, 마진 관리, 리비전 관리 기능 구현 --- ## 2. 테이블 스키마 (구현 완료) ### 2.1 `prices` 테이블 (신규 생성) ✅ ```sql CREATE TABLE prices ( id BIGINT PRIMARY KEY AUTO_INCREMENT, tenant_id BIGINT NOT NULL, -- 품목 연결 item_type_code VARCHAR(20) NOT NULL, -- 'PRODUCT' | 'MATERIAL' item_id BIGINT NOT NULL, client_group_id BIGINT NULL, -- NULL=기본가 -- 원가 정보 purchase_price DECIMAL(15,4) NULL, -- 매입단가 processing_cost DECIMAL(15,4) NULL, -- 가공비 loss_rate DECIMAL(5,2) NULL, -- LOSS율 (%) -- 판매가 정보 margin_rate DECIMAL(5,2) NULL, -- 마진율 (%) sales_price DECIMAL(15,4) NULL, -- 판매단가 rounding_rule ENUM('round','ceil','floor') DEFAULT 'round', rounding_unit INT DEFAULT 1, -- 1, 10, 100, 1000 -- 메타 정보 supplier VARCHAR(255) NULL, effective_from DATE NOT NULL, effective_to DATE NULL, note TEXT NULL, -- 상태 관리 status ENUM('draft','active','inactive','finalized') DEFAULT 'draft', is_final BOOLEAN DEFAULT FALSE, finalized_at DATETIME NULL, finalized_by BIGINT NULL, -- 감사 컬럼 created_by, updated_by, deleted_by, timestamps, soft_deletes ); ``` ### 2.2 `price_revisions` 테이블 (변경 이력) ✅ ```sql CREATE TABLE price_revisions ( id BIGINT PRIMARY KEY, tenant_id BIGINT NOT NULL, price_id BIGINT NOT NULL, revision_number INT NOT NULL, changed_at DATETIME NOT NULL, changed_by BIGINT NOT NULL, change_reason VARCHAR(500) NULL, before_snapshot JSON NULL, after_snapshot JSON NOT NULL ); ``` ### 2.3 기존 `price_histories` 테이블 처리 ✅ - ✅ `prices` 테이블로 데이터 마이그레이션 완료 - ✅ `price_histories` 테이블 삭제됨 --- ## 3. API 엔드포인트 상세 (구현 완료) ### 3.1 단가 등록 `POST /api/v1/pricing` ✅ **Request Body:** ```json { "item_type_code": "MATERIAL", "item_id": 123, "client_group_id": null, "purchase_price": 1000, "processing_cost": 100, "loss_rate": 5, "margin_rate": 20, "rounding_rule": "round", "rounding_unit": 10, "supplier": "ABC상사", "effective_from": "2025-01-01", "effective_to": null, "note": "2025년 1분기 단가", "status": "active" } ``` **자동 처리:** - `sales_price` 자동 계산 (입력 안해도 됨) - 기존 무기한 단가의 `effective_to` 자동 설정 - 최초 리비전 자동 생성 ### 3.2 목록 조회 `GET /api/v1/pricing` ✅ **Query Parameters:** | 파라미터 | 타입 | 설명 | |---------|------|------| | `page` | int | 페이지 번호 | | `size` | int | 페이지당 개수 (max 100) | | `q` | string | 검색어 (공급업체, 비고) | | `item_type_code` | string | `PRODUCT` / `MATERIAL` | | `item_id` | int | 품목 ID | | `client_group_id` | int/null | 고객그룹 ID | | `status` | string | `draft`, `active`, `inactive`, `finalized` | | `valid_at` | date | 특정 일자에 유효한 단가 | **Response:** ```json { "success": true, "message": "message.fetched", "data": { "current_page": 1, "data": [ { "id": 1, "item_type_code": "MATERIAL", "item_id": 123, "client_group_id": null, "purchase_price": "1000.0000", "processing_cost": "100.0000", "loss_rate": "5.00", "margin_rate": "20.00", "sales_price": "1386.0000", "rounding_rule": "round", "rounding_unit": 10, "supplier": "ABC상사", "effective_from": "2025-01-01", "effective_to": null, "status": "active", "is_final": false, "client_group": null } ], "total": 1 } } ``` --- ## 4. 추가 API 엔드포인트 (구현 완료) ### 4.1 품목별 단가 현황 `POST /api/v1/pricing/by-items` ✅ **용도**: 여러 품목의 현재 유효한 단가를 한번에 조회 **Request Body:** ```json { "items": [ { "item_type_code": "MATERIAL", "item_id": 123 }, { "item_type_code": "MATERIAL", "item_id": 124 }, { "item_type_code": "PRODUCT", "item_id": 10 } ], "client_group_id": 1, "date": "2025-12-08" } ``` **조회 우선순위:** 1. 고객그룹별 단가 (`client_group_id` 지정 시) 2. 기본 단가 (`client_group_id = NULL`) **Response:** ```json { "success": true, "data": [ { "item_type_code": "MATERIAL", "item_id": 123, "price": { ... }, "has_price": true }, { "item_type_code": "MATERIAL", "item_id": 124, "price": null, "has_price": false } ] } ``` ### 4.2 변경 이력 조회 `GET /api/v1/pricing/{id}/revisions` ✅ **Response:** ```json { "success": true, "data": { "data": [ { "id": 2, "revision_number": 2, "changed_at": "2025-12-08T11:00:00.000000Z", "changed_by": 1, "change_reason": "마진율 조정", "before_snapshot": { "purchase_price": "1000.0000", "margin_rate": "15.00", "sales_price": "1265.0000" }, "after_snapshot": { "purchase_price": "1000.0000", "margin_rate": "20.00", "sales_price": "1386.0000" }, "changed_by_user": { "id": 1, "name": "홍길동" } } ] } } ``` ### 4.3 단가 확정 `POST /api/v1/pricing/{id}/finalize` ✅ **동작:** - `is_final` → `true` - `status` → `finalized` - 확정 후 **수정/삭제 불가** **Response:** ```json { "success": true, "message": "message.pricing.finalized", "data": { "id": 5, "is_final": true, "finalized_at": "2025-12-08T14:30:00.000000Z", "finalized_by": 1, "status": "finalized" } } ``` ### 4.4 원가 조회 `GET /api/v1/pricing/cost` ✅ **용도**: 수입검사 입고단가 > 표준원가 우선순위로 원가 조회 **Query Parameters:** | 파라미터 | 타입 | 필수 | 설명 | |---------|------|------|------| | `item_type_code` | string | ✅ | `PRODUCT` / `MATERIAL` | | `item_id` | int | ✅ | 품목 ID | | `date` | date | ❌ | 조회 기준일 (기본: 오늘) | **Response:** ```json { "success": true, "data": { "item_type_code": "MATERIAL", "item_id": 123, "date": "2025-12-08", "cost_source": "receipt", "purchase_price": 1050.00, "receipt_id": 45, "receipt_date": "2025-12-01", "price_id": null } } ``` | cost_source | 설명 | |-------------|------| | `receipt` | 수입검사 입고단가 | | `standard` | 표준원가 (prices 테이블) | | `not_found` | 단가 미등록 | --- ## 5. 비즈니스 로직 ### 5.1 판매단가 자동 계산 ``` 총원가 = (매입단가 + 가공비) × (1 + LOSS율/100) 판매단가 = 반올림(총원가 × (1 + 마진율/100), 반올림단위, 반올림규칙) ``` ### 5.2 상태 흐름 ``` draft → active → finalized ↓ inactive ``` ### 5.3 주요 검증 규칙 - 동일 품목+고객그룹+시작일 조합 중복 불가 - 확정된 단가는 수정/삭제 불가 - 마진율/LOSS율은 0~100% 범위 - 반올림단위는 1, 10, 100, 1000 중 하나 --- ## 6. 프론트엔드 작업 현황 | 작업 | 상태 | 비고 | |------|------|------| | `usePricingList` 훅 생성 | ⬜ 미완료 | | | 타입 정의 | ⬜ 미완료 | | | 단가 목록 페이지 | ⬜ 미완료 | | | 단가 등록/수정 페이지 | ⬜ 미완료 | | | 단가 상세 페이지 (이력 포함) | ⬜ 미완료 | | | 품목별 단가 현황 컴포넌트 | ⬜ 미완료 | | --- ## 7. 백엔드 참고 파일 ### 컨트롤러/서비스 - `api/app/Http/Controllers/Api/V1/PricingController.php` - `api/app/Services/PricingService.php` ### 모델 - `api/app/Models/Products/Price.php` - `api/app/Models/Products/PriceRevision.php` ### 요청 클래스 - `api/app/Http/Requests/Pricing/PriceIndexRequest.php` - `api/app/Http/Requests/Pricing/PriceStoreRequest.php` - `api/app/Http/Requests/Pricing/PriceUpdateRequest.php` - `api/app/Http/Requests/Pricing/PriceByItemsRequest.php` - `api/app/Http/Requests/Pricing/PriceCostRequest.php` ### 마이그레이션 - `api/database/migrations/2025_12_08_154633_create_prices_table.php` - `api/database/migrations/2025_12_08_154634_create_price_revisions_table.php` - `api/database/migrations/2025_12_08_154635_migrate_price_histories_to_prices.php` - `api/database/migrations/2025_12_08_154636_drop_price_histories_table.php` ### 라우트 - `api/routes/api.php` (Line 368-379) --- ## 8. 관련 문서 - [단가 정책](../rules/pricing-policy.md) - 상세 정책 및 계산 공식 --- **최종 업데이트**: 2025-12-08