- 작업지시 테이블 마이그레이션 (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>
417 lines
18 KiB
PHP
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() {}
|
|
}
|