- 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>
368 lines
16 KiB
PHP
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() {}
|
|
}
|