Files
sam-api/app/Swagger/v1/OrderApi.php
kent de19ac97aa feat: 수주관리(Order Management) API Phase 1.1 구현
- OrderService 구현 (index, stats, show, store, update, destroy, updateStatus)
- OrderController 구현 (7개 API 엔드포인트)
- FormRequest 클래스 3개 생성 (Store, Update, UpdateStatus)
- 상태 전환 규칙 검증 (DRAFT → CONFIRMED → IN_PROGRESS → COMPLETED/CANCELLED)
- 수주번호 자동 생성 (ORD{YYYYMMDD}{0001} 형식)
- Swagger API 문서 작성 (OrderApi.php)
- i18n 메시지 키 추가 (ko/en)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 11:11:54 +09:00

368 lines
16 KiB
PHP

<?php
namespace App\Swagger\v1;
/**
* @OA\Tag(
* name="Order",
* description="수주관리 API"
* )
*
* @OA\Schema(
* schema="Order",
* type="object",
*
* @OA\Property(property="id", type="integer", example=1),
* @OA\Property(property="tenant_id", type="integer", example=1),
* @OA\Property(property="quote_id", type="integer", nullable=true, example=1),
* @OA\Property(property="order_no", type="string", example="ORD202501080001"),
* @OA\Property(property="order_type_code", type="string", enum={"ORDER", "PURCHASE"}, example="ORDER"),
* @OA\Property(property="status_code", type="string", enum={"DRAFT", "CONFIRMED", "IN_PROGRESS", "COMPLETED", "CANCELLED"}, example="DRAFT"),
* @OA\Property(property="category_code", type="string", nullable=true, example="GENERAL"),
* @OA\Property(property="client_id", type="integer", nullable=true, example=1),
* @OA\Property(property="client_name", type="string", nullable=true, example="ABC 기업"),
* @OA\Property(property="client_contact", type="string", nullable=true, example="010-1234-5678"),
* @OA\Property(property="site_name", type="string", nullable=true, example="강남 현장"),
* @OA\Property(property="quantity", type="number", format="float", example=100),
* @OA\Property(property="supply_amount", type="number", format="float", example=1000000),
* @OA\Property(property="tax_amount", type="number", format="float", example=100000),
* @OA\Property(property="total_amount", type="number", format="float", example=1100000),
* @OA\Property(property="discount_rate", type="number", format="float", nullable=true, example=5),
* @OA\Property(property="discount_amount", type="number", format="float", nullable=true, example=50000),
* @OA\Property(property="delivery_date", type="string", format="date", nullable=true, example="2025-01-15"),
* @OA\Property(property="delivery_method_code", type="string", nullable=true, example="DELIVERY"),
* @OA\Property(property="received_at", type="string", format="date-time", nullable=true),
* @OA\Property(property="memo", type="string", nullable=true),
* @OA\Property(property="remarks", type="string", nullable=true),
* @OA\Property(property="note", type="string", nullable=true),
* @OA\Property(property="created_by", type="integer", nullable=true),
* @OA\Property(property="updated_by", type="integer", nullable=true),
* @OA\Property(property="created_at", type="string", format="date-time"),
* @OA\Property(property="updated_at", type="string", format="date-time"),
* @OA\Property(property="items", type="array", @OA\Items(ref="#/components/schemas/OrderItem")),
* @OA\Property(property="client", type="object", nullable=true)
* )
*
* @OA\Schema(
* schema="OrderItem",
* type="object",
*
* @OA\Property(property="id", type="integer", example=1),
* @OA\Property(property="order_id", type="integer", example=1),
* @OA\Property(property="item_id", type="integer", nullable=true, example=1),
* @OA\Property(property="item_name", type="string", example="제품A"),
* @OA\Property(property="specification", type="string", nullable=true, example="100x200mm"),
* @OA\Property(property="quantity", type="number", format="float", example=10),
* @OA\Property(property="unit", type="string", nullable=true, example="EA"),
* @OA\Property(property="unit_price", type="number", format="float", example=10000),
* @OA\Property(property="supply_amount", type="number", format="float", example=100000),
* @OA\Property(property="tax_amount", type="number", format="float", example=10000),
* @OA\Property(property="total_amount", type="number", format="float", example=110000),
* @OA\Property(property="sort_order", type="integer", example=0)
* )
*
* @OA\Schema(
* schema="OrderPagination",
* type="object",
*
* @OA\Property(property="current_page", type="integer", example=1),
* @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/Order")),
* @OA\Property(property="first_page_url", type="string"),
* @OA\Property(property="from", type="integer"),
* @OA\Property(property="last_page", type="integer"),
* @OA\Property(property="last_page_url", type="string"),
* @OA\Property(property="next_page_url", type="string", nullable=true),
* @OA\Property(property="path", type="string"),
* @OA\Property(property="per_page", type="integer"),
* @OA\Property(property="prev_page_url", type="string", nullable=true),
* @OA\Property(property="to", type="integer"),
* @OA\Property(property="total", type="integer")
* )
*
* @OA\Schema(
* schema="OrderStats",
* type="object",
*
* @OA\Property(property="total", type="integer", example=100),
* @OA\Property(property="draft", type="integer", example=10),
* @OA\Property(property="confirmed", type="integer", example=30),
* @OA\Property(property="in_progress", type="integer", example=40),
* @OA\Property(property="completed", type="integer", example=15),
* @OA\Property(property="cancelled", type="integer", example=5),
* @OA\Property(property="total_amount", type="number", format="float", example=50000000),
* @OA\Property(property="confirmed_amount", type="number", format="float", example=35000000)
* )
*
* @OA\Schema(
* schema="OrderCreateRequest",
* type="object",
*
* @OA\Property(property="quote_id", type="integer", nullable=true),
* @OA\Property(property="order_type_code", type="string", enum={"ORDER", "PURCHASE"}, example="ORDER"),
* @OA\Property(property="status_code", type="string", enum={"DRAFT", "CONFIRMED"}, example="DRAFT"),
* @OA\Property(property="category_code", type="string", nullable=true),
* @OA\Property(property="client_id", type="integer", nullable=true),
* @OA\Property(property="client_name", type="string", nullable=true),
* @OA\Property(property="client_contact", type="string", nullable=true),
* @OA\Property(property="site_name", type="string", nullable=true),
* @OA\Property(property="delivery_date", type="string", format="date", nullable=true),
* @OA\Property(property="delivery_method_code", type="string", nullable=true),
* @OA\Property(property="received_at", type="string", format="date", nullable=true),
* @OA\Property(property="memo", type="string", nullable=true),
* @OA\Property(property="remarks", type="string", nullable=true),
* @OA\Property(property="note", type="string", nullable=true),
* @OA\Property(property="items", type="array", @OA\Items(ref="#/components/schemas/OrderItemRequest"))
* )
*
* @OA\Schema(
* schema="OrderUpdateRequest",
* type="object",
*
* @OA\Property(property="order_type_code", type="string", enum={"ORDER", "PURCHASE"}),
* @OA\Property(property="category_code", type="string", nullable=true),
* @OA\Property(property="client_id", type="integer", nullable=true),
* @OA\Property(property="client_name", type="string", nullable=true),
* @OA\Property(property="client_contact", type="string", nullable=true),
* @OA\Property(property="site_name", type="string", nullable=true),
* @OA\Property(property="delivery_date", type="string", format="date", nullable=true),
* @OA\Property(property="delivery_method_code", type="string", nullable=true),
* @OA\Property(property="received_at", type="string", format="date", nullable=true),
* @OA\Property(property="memo", type="string", nullable=true),
* @OA\Property(property="remarks", type="string", nullable=true),
* @OA\Property(property="note", type="string", nullable=true),
* @OA\Property(property="items", type="array", nullable=true, @OA\Items(ref="#/components/schemas/OrderItemRequest"))
* )
*
* @OA\Schema(
* schema="OrderItemRequest",
* type="object",
* required={"item_name", "quantity", "unit_price"},
*
* @OA\Property(property="item_id", type="integer", nullable=true),
* @OA\Property(property="item_name", type="string", example="제품A"),
* @OA\Property(property="specification", type="string", nullable=true),
* @OA\Property(property="quantity", type="number", format="float", example=10),
* @OA\Property(property="unit", type="string", nullable=true, example="EA"),
* @OA\Property(property="unit_price", type="number", format="float", example=10000)
* )
*
* @OA\Schema(
* schema="OrderStatusRequest",
* type="object",
* required={"status"},
*
* @OA\Property(property="status", type="string", enum={"DRAFT", "CONFIRMED", "IN_PROGRESS", "COMPLETED", "CANCELLED"}, example="CONFIRMED")
* )
*/
class OrderApi
{
/**
* @OA\Get(
* path="/api/v1/orders",
* tags={"Order"},
* summary="수주 목록 조회",
* description="수주 목록을 페이징하여 조회합니다.",
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
*
* @OA\Parameter(name="page", in="query", @OA\Schema(type="integer", default=1)),
* @OA\Parameter(name="size", in="query", @OA\Schema(type="integer", default=20)),
* @OA\Parameter(name="q", in="query", description="검색어 (수주번호, 현장명, 거래처명)", @OA\Schema(type="string")),
* @OA\Parameter(name="status", in="query", description="상태 필터", @OA\Schema(type="string", enum={"DRAFT", "CONFIRMED", "IN_PROGRESS", "COMPLETED", "CANCELLED"})),
* @OA\Parameter(name="order_type", in="query", description="주문유형 필터", @OA\Schema(type="string", enum={"ORDER", "PURCHASE"})),
* @OA\Parameter(name="client_id", in="query", description="거래처 ID", @OA\Schema(type="integer")),
* @OA\Parameter(name="date_from", in="query", description="수주일 시작", @OA\Schema(type="string", format="date")),
* @OA\Parameter(name="date_to", in="query", description="수주일 종료", @OA\Schema(type="string", format="date")),
*
* @OA\Response(
* response=200,
* description="성공",
*
* @OA\JsonContent(
*
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(property="message", type="string"),
* @OA\Property(property="data", ref="#/components/schemas/OrderPagination")
* )
* )
* )
*/
public function index() {}
/**
* @OA\Get(
* path="/api/v1/orders/stats",
* tags={"Order"},
* summary="수주 통계 조회",
* description="상태별 수주 건수와 금액을 조회합니다.",
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
*
* @OA\Response(
* response=200,
* description="성공",
*
* @OA\JsonContent(
*
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(property="message", type="string"),
* @OA\Property(property="data", ref="#/components/schemas/OrderStats")
* )
* )
* )
*/
public function stats() {}
/**
* @OA\Get(
* path="/api/v1/orders/{id}",
* tags={"Order"},
* summary="수주 상세 조회",
* description="특정 수주의 상세 정보를 조회합니다.",
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
*
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
*
* @OA\Response(
* response=200,
* description="성공",
*
* @OA\JsonContent(
*
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(property="message", type="string"),
* @OA\Property(property="data", ref="#/components/schemas/Order")
* )
* ),
*
* @OA\Response(response=404, description="수주를 찾을 수 없음")
* )
*/
public function show() {}
/**
* @OA\Post(
* path="/api/v1/orders",
* tags={"Order"},
* summary="수주 생성",
* description="새로운 수주를 생성합니다.",
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
*
* @OA\RequestBody(
* required=true,
*
* @OA\JsonContent(ref="#/components/schemas/OrderCreateRequest")
* ),
*
* @OA\Response(
* response=200,
* description="성공",
*
* @OA\JsonContent(
*
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(property="message", type="string"),
* @OA\Property(property="data", ref="#/components/schemas/Order")
* )
* ),
*
* @OA\Response(response=422, description="유효성 검증 실패")
* )
*/
public function store() {}
/**
* @OA\Put(
* path="/api/v1/orders/{id}",
* tags={"Order"},
* summary="수주 수정",
* description="기존 수주를 수정합니다. 완료/취소 상태에서는 수정할 수 없습니다.",
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
*
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
*
* @OA\RequestBody(
* required=true,
*
* @OA\JsonContent(ref="#/components/schemas/OrderUpdateRequest")
* ),
*
* @OA\Response(
* response=200,
* description="성공",
*
* @OA\JsonContent(
*
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(property="message", type="string"),
* @OA\Property(property="data", ref="#/components/schemas/Order")
* )
* ),
*
* @OA\Response(response=400, description="수정 불가 상태"),
* @OA\Response(response=404, description="수주를 찾을 수 없음")
* )
*/
public function update() {}
/**
* @OA\Delete(
* path="/api/v1/orders/{id}",
* tags={"Order"},
* summary="수주 삭제",
* description="수주를 삭제합니다. 진행 중이거나 완료된 수주는 삭제할 수 없습니다.",
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
*
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
*
* @OA\Response(
* response=200,
* description="성공",
*
* @OA\JsonContent(
*
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(property="message", type="string"),
* @OA\Property(property="data", type="string", example="success")
* )
* ),
*
* @OA\Response(response=400, description="삭제 불가 상태"),
* @OA\Response(response=404, description="수주를 찾을 수 없음")
* )
*/
public function destroy() {}
/**
* @OA\Patch(
* path="/api/v1/orders/{id}/status",
* tags={"Order"},
* summary="수주 상태 변경",
* description="수주의 상태를 변경합니다. 상태 전환 규칙에 따라 유효한 상태로만 변경 가능합니다.",
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
*
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
*
* @OA\RequestBody(
* required=true,
*
* @OA\JsonContent(ref="#/components/schemas/OrderStatusRequest")
* ),
*
* @OA\Response(
* response=200,
* description="성공",
*
* @OA\JsonContent(
*
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(property="message", type="string"),
* @OA\Property(property="data", ref="#/components/schemas/Order")
* )
* ),
*
* @OA\Response(response=400, description="유효하지 않은 상태 전환"),
* @OA\Response(response=404, description="수주를 찾을 수 없음")
* )
*/
public function updateStatus() {}
}