Files
sam-api/app/Swagger/v1/WorkOrderApi.php
kent 05a53cdc8e feat: G-1 작업지시 관리 API 구현
- 작업지시 테이블 마이그레이션 (work_orders, work_order_items, work_order_bending_details, work_order_issues)
- 작업지시 모델 4개 (WorkOrder, WorkOrderItem, WorkOrderBendingDetail, WorkOrderIssue)
- WorkOrderService 비즈니스 로직 구현
- WorkOrderController REST API 엔드포인트 11개
- FormRequest 검증 클래스 5개
- Swagger API 문서화 완료

API Endpoints:
- GET /work-orders (목록)
- GET /work-orders/stats (통계)
- POST /work-orders (등록)
- GET /work-orders/{id} (상세)
- PUT /work-orders/{id} (수정)
- DELETE /work-orders/{id} (삭제)
- PATCH /work-orders/{id}/status (상태변경)
- PATCH /work-orders/{id}/assign (담당자배정)
- PATCH /work-orders/{id}/bending/toggle (벤딩토글)
- POST /work-orders/{id}/issues (이슈등록)
- PATCH /work-orders/{id}/issues/{issueId}/resolve (이슈해결)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-26 13:57:42 +09:00

417 lines
18 KiB
PHP

<?php
namespace App\Swagger\v1;
/**
* @OA\Tag(name="WorkOrder", description="작업지시 관리")
*
* @OA\Schema(
* schema="WorkOrder",
* type="object",
* required={"id","work_order_no","process_type","status"},
*
* @OA\Property(property="id", type="integer", example=1),
* @OA\Property(property="tenant_id", type="integer", example=1),
* @OA\Property(property="work_order_no", type="string", example="WO202512260001"),
* @OA\Property(property="sales_order_id", type="integer", nullable=true, example=1),
* @OA\Property(property="project_name", type="string", nullable=true, example="강남빌딩 방충망"),
* @OA\Property(property="process_type", type="string", enum={"screen","slat","bending"}, example="screen"),
* @OA\Property(property="status", type="string", enum={"unassigned","pending","waiting","in_progress","completed","shipped"}, example="pending"),
* @OA\Property(property="assignee_id", type="integer", nullable=true, example=10),
* @OA\Property(property="team_id", type="integer", nullable=true, example=5),
* @OA\Property(property="scheduled_date", type="string", format="date", nullable=true, example="2025-12-28"),
* @OA\Property(property="started_at", type="string", format="date-time", nullable=true),
* @OA\Property(property="completed_at", type="string", format="date-time", nullable=true),
* @OA\Property(property="shipped_at", type="string", format="date-time", nullable=true),
* @OA\Property(property="memo", type="string", nullable=true),
* @OA\Property(property="is_active", type="boolean", example=true),
* @OA\Property(property="created_at", type="string", example="2025-12-26"),
* @OA\Property(property="updated_at", type="string", example="2025-12-26"),
* @OA\Property(property="assignee", type="object", nullable=true, @OA\Property(property="id", type="integer"), @OA\Property(property="name", type="string")),
* @OA\Property(property="team", type="object", nullable=true, @OA\Property(property="id", type="integer"), @OA\Property(property="name", type="string"))
* )
*
* @OA\Schema(
* schema="WorkOrderItem",
* type="object",
*
* @OA\Property(property="id", type="integer", example=1),
* @OA\Property(property="work_order_id", type="integer", example=1),
* @OA\Property(property="item_id", type="integer", nullable=true, example=100),
* @OA\Property(property="item_name", type="string", example="방충망 프레임"),
* @OA\Property(property="specification", type="string", nullable=true, example="W1200 x H2400"),
* @OA\Property(property="quantity", type="number", format="float", example=10),
* @OA\Property(property="unit", type="string", nullable=true, example="EA"),
* @OA\Property(property="sort_order", type="integer", example=0)
* )
*
* @OA\Schema(
* schema="WorkOrderBendingDetail",
* type="object",
*
* @OA\Property(property="id", type="integer", example=1),
* @OA\Property(property="work_order_id", type="integer", example=1),
* @OA\Property(property="shaft_cutting", type="boolean", example=true),
* @OA\Property(property="bearing", type="boolean", example=false),
* @OA\Property(property="shaft_welding", type="boolean", example=false),
* @OA\Property(property="assembly", type="boolean", example=false),
* @OA\Property(property="winder_welding", type="boolean", example=false),
* @OA\Property(property="frame_assembly", type="boolean", example=false),
* @OA\Property(property="bundle_assembly", type="boolean", example=false),
* @OA\Property(property="motor_assembly", type="boolean", example=false),
* @OA\Property(property="bracket_assembly", type="boolean", example=false)
* )
*
* @OA\Schema(
* schema="WorkOrderIssue",
* type="object",
*
* @OA\Property(property="id", type="integer", example=1),
* @OA\Property(property="work_order_id", type="integer", example=1),
* @OA\Property(property="title", type="string", example="자재 부족"),
* @OA\Property(property="description", type="string", nullable=true, example="방충망 프레임 재고 부족"),
* @OA\Property(property="priority", type="string", enum={"high","medium","low"}, example="high"),
* @OA\Property(property="status", type="string", enum={"open","in_progress","resolved"}, example="open"),
* @OA\Property(property="reported_by", type="integer", nullable=true, example=10),
* @OA\Property(property="resolved_by", type="integer", nullable=true),
* @OA\Property(property="resolved_at", type="string", format="date-time", nullable=true)
* )
*
* @OA\Schema(
* schema="WorkOrderStats",
* type="object",
*
* @OA\Property(property="total", type="integer", example=100),
* @OA\Property(property="unassigned", type="integer", example=10),
* @OA\Property(property="pending", type="integer", example=15),
* @OA\Property(property="waiting", type="integer", example=5),
* @OA\Property(property="in_progress", type="integer", example=20),
* @OA\Property(property="completed", type="integer", example=40),
* @OA\Property(property="shipped", type="integer", example=10)
* )
*
* @OA\Schema(
* schema="WorkOrderPagination",
* type="object",
*
* @OA\Property(property="current_page", type="integer", example=1),
* @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/WorkOrder")),
* @OA\Property(property="first_page_url", type="string"),
* @OA\Property(property="from", type="integer"),
* @OA\Property(property="last_page", type="integer"),
* @OA\Property(property="per_page", type="integer"),
* @OA\Property(property="total", type="integer")
* )
*
* @OA\Schema(
* schema="WorkOrderCreateRequest",
* type="object",
* required={"process_type"},
*
* @OA\Property(property="sales_order_id", type="integer", nullable=true),
* @OA\Property(property="project_name", type="string", nullable=true, maxLength=200),
* @OA\Property(property="process_type", type="string", enum={"screen","slat","bending"}),
* @OA\Property(property="assignee_id", type="integer", nullable=true),
* @OA\Property(property="team_id", type="integer", nullable=true),
* @OA\Property(property="scheduled_date", type="string", format="date", nullable=true),
* @OA\Property(property="memo", type="string", nullable=true),
* @OA\Property(property="items", type="array", @OA\Items(
* type="object",
* @OA\Property(property="item_id", type="integer", nullable=true),
* @OA\Property(property="item_name", type="string"),
* @OA\Property(property="specification", type="string", nullable=true),
* @OA\Property(property="quantity", type="number", nullable=true),
* @OA\Property(property="unit", type="string", nullable=true)
* )),
* @OA\Property(property="bending_detail", type="object", ref="#/components/schemas/WorkOrderBendingDetail")
* )
*
* @OA\Schema(
* schema="WorkOrderUpdateRequest",
* type="object",
*
* @OA\Property(property="sales_order_id", type="integer", nullable=true),
* @OA\Property(property="project_name", type="string", nullable=true),
* @OA\Property(property="process_type", type="string", enum={"screen","slat","bending"}, nullable=true),
* @OA\Property(property="status", type="string", enum={"unassigned","pending","waiting","in_progress","completed","shipped"}, nullable=true),
* @OA\Property(property="assignee_id", type="integer", nullable=true),
* @OA\Property(property="team_id", type="integer", nullable=true),
* @OA\Property(property="scheduled_date", type="string", format="date", nullable=true),
* @OA\Property(property="memo", type="string", nullable=true),
* @OA\Property(property="items", type="array", nullable=true, @OA\Items(type="object")),
* @OA\Property(property="bending_detail", type="object", nullable=true)
* )
*/
class WorkOrderApi
{
/**
* @OA\Get(
* path="/api/v1/work-orders",
* tags={"WorkOrder"},
* summary="작업지시 목록",
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
*
* @OA\Parameter(name="page", in="query", @OA\Schema(type="integer", example=1)),
* @OA\Parameter(name="size", in="query", @OA\Schema(type="integer", example=20)),
* @OA\Parameter(name="q", in="query", description="작업지시번호/프로젝트명 검색", @OA\Schema(type="string")),
* @OA\Parameter(name="status", in="query", description="상태 필터", @OA\Schema(type="string", enum={"unassigned","pending","waiting","in_progress","completed","shipped"})),
* @OA\Parameter(name="process_type", in="query", description="공정유형 필터", @OA\Schema(type="string", enum={"screen","slat","bending"})),
* @OA\Parameter(name="assignee_id", in="query", description="담당자 필터", @OA\Schema(type="integer")),
* @OA\Parameter(name="team_id", in="query", description="팀 필터", @OA\Schema(type="integer")),
* @OA\Parameter(name="scheduled_from", in="query", description="예정일 시작", @OA\Schema(type="string", format="date")),
* @OA\Parameter(name="scheduled_to", in="query", description="예정일 종료", @OA\Schema(type="string", format="date")),
*
* @OA\Response(response=200, description="조회 성공",
*
* @OA\JsonContent(allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/WorkOrderPagination"))
* })
* ),
*
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function index() {}
/**
* @OA\Get(
* path="/api/v1/work-orders/stats",
* tags={"WorkOrder"},
* summary="작업지시 통계",
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
*
* @OA\Response(response=200, description="조회 성공",
*
* @OA\JsonContent(allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/WorkOrderStats"))
* })
* )
* )
*/
public function stats() {}
/**
* @OA\Get(
* path="/api/v1/work-orders/{id}",
* tags={"WorkOrder"},
* summary="작업지시 단건 조회",
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
*
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
*
* @OA\Response(response=200, description="조회 성공",
*
* @OA\JsonContent(allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/WorkOrder"))
* })
* ),
*
* @OA\Response(response=404, description="데이터 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function show() {}
/**
* @OA\Post(
* path="/api/v1/work-orders",
* tags={"WorkOrder"},
* summary="작업지시 생성",
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
*
* @OA\RequestBody(required=true, @OA\JsonContent(ref="#/components/schemas/WorkOrderCreateRequest")),
*
* @OA\Response(response=200, description="생성 성공",
*
* @OA\JsonContent(allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/WorkOrder"))
* })
* ),
*
* @OA\Response(response=400, description="검증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function store() {}
/**
* @OA\Put(
* path="/api/v1/work-orders/{id}",
* tags={"WorkOrder"},
* summary="작업지시 수정",
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
*
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
*
* @OA\RequestBody(required=true, @OA\JsonContent(ref="#/components/schemas/WorkOrderUpdateRequest")),
*
* @OA\Response(response=200, description="수정 성공",
*
* @OA\JsonContent(allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/WorkOrder"))
* })
* ),
*
* @OA\Response(response=404, description="데이터 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function update() {}
/**
* @OA\Delete(
* path="/api/v1/work-orders/{id}",
* tags={"WorkOrder"},
* summary="작업지시 삭제",
* description="진행 중이거나 완료된 작업지시는 삭제할 수 없습니다.",
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
*
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
*
* @OA\Response(response=200, description="삭제 성공", @OA\JsonContent(ref="#/components/schemas/ApiResponse")),
* @OA\Response(response=400, description="삭제 불가", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
* @OA\Response(response=404, description="데이터 없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function destroy() {}
/**
* @OA\Patch(
* path="/api/v1/work-orders/{id}/status",
* tags={"WorkOrder"},
* summary="상태 변경",
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
*
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
*
* @OA\RequestBody(required=true, @OA\JsonContent(
*
* @OA\Property(property="status", type="string", enum={"unassigned","pending","waiting","in_progress","completed","shipped"})
* )),
*
* @OA\Response(response=200, description="변경 성공",
*
* @OA\JsonContent(allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/WorkOrder"))
* })
* )
* )
*/
public function updateStatus() {}
/**
* @OA\Patch(
* path="/api/v1/work-orders/{id}/assign",
* tags={"WorkOrder"},
* summary="담당자 배정",
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
*
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
*
* @OA\RequestBody(required=true, @OA\JsonContent(
*
* @OA\Property(property="assignee_id", type="integer", description="담당자 ID"),
* @OA\Property(property="team_id", type="integer", nullable=true, description="팀 ID")
* )),
*
* @OA\Response(response=200, description="배정 성공",
*
* @OA\JsonContent(allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/WorkOrder"))
* })
* )
* )
*/
public function assign() {}
/**
* @OA\Patch(
* path="/api/v1/work-orders/{id}/bending/toggle",
* tags={"WorkOrder"},
* summary="벤딩 항목 토글",
* description="벤딩 공정의 세부 항목 완료 여부를 토글합니다.",
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
*
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
*
* @OA\RequestBody(required=true, @OA\JsonContent(
*
* @OA\Property(property="field", type="string", enum={"shaft_cutting","bearing","shaft_welding","assembly","winder_welding","frame_assembly","bundle_assembly","motor_assembly","bracket_assembly"})
* )),
*
* @OA\Response(response=200, description="토글 성공",
*
* @OA\JsonContent(allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/WorkOrderBendingDetail"))
* })
* ),
*
* @OA\Response(response=400, description="벤딩 공정이 아님", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
* )
*/
public function toggleBendingField() {}
/**
* @OA\Post(
* path="/api/v1/work-orders/{id}/issues",
* tags={"WorkOrder"},
* summary="이슈 추가",
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
*
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
*
* @OA\RequestBody(required=true, @OA\JsonContent(
*
* @OA\Property(property="title", type="string", maxLength=200),
* @OA\Property(property="description", type="string", nullable=true),
* @OA\Property(property="priority", type="string", enum={"high","medium","low"}, nullable=true)
* )),
*
* @OA\Response(response=200, description="추가 성공",
*
* @OA\JsonContent(allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/WorkOrderIssue"))
* })
* )
* )
*/
public function addIssue() {}
/**
* @OA\Patch(
* path="/api/v1/work-orders/{workOrderId}/issues/{issueId}/resolve",
* tags={"WorkOrder"},
* summary="이슈 해결",
* security={{"ApiKeyAuth":{}},{"BearerAuth":{}}},
*
* @OA\Parameter(name="workOrderId", in="path", required=true, @OA\Schema(type="integer")),
* @OA\Parameter(name="issueId", in="path", required=true, @OA\Schema(type="integer")),
*
* @OA\Response(response=200, description="해결 성공",
*
* @OA\JsonContent(allOf={
*
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/WorkOrderIssue"))
* })
* )
* )
*/
public function resolveIssue() {}
}