Files
sam-api/app/Swagger/v1/OrderApi.php
권혁성 c637dd38eb docs(WEB): 수주 Swagger 문서 추가 - bulkDestroy, revertProductionOrder
- OrderBulkDeleteRequest 스키마 추가 (ids, force)
- OrderRevertProductionRequest 스키마 추가 (force, reason)
- DELETE /api/v1/orders/bulk 엔드포인트 문서 추가
- POST /api/v1/orders/{id}/revert-production 엔드포인트 문서 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 04:19:47 +09:00

467 lines
21 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")
* )
*
* @OA\Schema(
* schema="OrderBulkDeleteRequest",
* type="object",
* required={"ids"},
*
* @OA\Property(property="ids", type="array", @OA\Items(type="integer"), example={1, 2, 3}),
* @OA\Property(property="force", type="boolean", description="강제 삭제 여부 (진행중 수주 포함)", example=false)
* )
*
* @OA\Schema(
* schema="OrderRevertProductionRequest",
* type="object",
*
* @OA\Property(property="force", type="boolean", description="강제 되돌리기 (물리 삭제, 기본값 false)", example=false),
* @OA\Property(property="reason", type="string", description="되돌리기 사유 (운영 모드 시 필수)", example="고객 요청에 의한 생산지시 취소")
* )
*/
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() {}
/**
* @OA\Delete(
* path="/api/v1/orders/bulk",
* tags={"Order"},
* summary="수주 일괄 삭제",
* description="여러 수주를 일괄 삭제합니다 (Soft Delete). 진행중/완료 수주는 건너뜁니다.",
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
*
* @OA\RequestBody(
* required=true,
*
* @OA\JsonContent(ref="#/components/schemas/OrderBulkDeleteRequest")
* ),
*
* @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="object",
* @OA\Property(property="deleted_count", type="integer", example=3),
* @OA\Property(property="skipped_count", type="integer", example=1),
* @OA\Property(property="skipped_ids", type="array", @OA\Items(type="integer"), example={5})
* )
* )
* ),
*
* @OA\Response(response=422, description="유효성 검증 실패")
* )
*/
public function bulkDestroy() {}
/**
* @OA\Post(
* path="/api/v1/orders/{id}/revert-production",
* tags={"Order"},
* summary="생산지시 되돌리기",
* description="생산지시를 되돌립니다. 기본 모드(force=false)에서는 작업지시를 취소 처리하며, 강제 모드(force=true)에서는 물리 삭제합니다.",
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
*
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
*
* @OA\RequestBody(
* required=false,
*
* @OA\JsonContent(ref="#/components/schemas/OrderRevertProductionRequest")
* ),
*
* @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="object",
* @OA\Property(property="order", ref="#/components/schemas/Order"),
* @OA\Property(property="deleted_counts", type="object",
* @OA\Property(property="work_results", type="integer", example=0),
* @OA\Property(property="work_order_items", type="integer", example=5),
* @OA\Property(property="work_orders", type="integer", example=2)
* ),
* @OA\Property(property="cancelled_counts", type="object", nullable=true,
* @OA\Property(property="work_orders", type="integer", example=2),
* @OA\Property(property="work_order_items", type="integer", example=5)
* ),
* @OA\Property(property="previous_status", type="string", example="IN_PROGRESS")
* )
* )
* ),
*
* @OA\Response(response=400, description="되돌리기 불가 상태 (수주확정/수주등록 상태)"),
* @OA\Response(response=404, description="수주를 찾을 수 없음"),
* @OA\Response(response=422, description="운영 모드에서 사유 미입력")
* )
*/
public function revertProductionOrder() {}
}