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>
This commit is contained in:
2025-12-26 13:57:42 +09:00
parent 5ab5353d4d
commit 05a53cdc8e
17 changed files with 2000 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* 작업지시 테이블 (Production Work Orders)
*/
public function up(): void
{
Schema::create('work_orders', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('tenant_id')->comment('테넌트ID');
$table->string('work_order_no', 50)->comment('작업지시번호');
$table->unsignedBigInteger('sales_order_id')->nullable()->comment('수주ID');
$table->string('project_name', 200)->nullable()->comment('프로젝트명');
$table->string('process_type', 30)->comment('공정유형: screen/slat/bending');
$table->string('status', 30)->default('unassigned')->comment('상태: unassigned/pending/waiting/in_progress/completed/shipped');
$table->unsignedBigInteger('assignee_id')->nullable()->comment('담당자ID');
$table->unsignedBigInteger('team_id')->nullable()->comment('팀ID');
$table->date('scheduled_date')->nullable()->comment('예정일');
$table->timestamp('started_at')->nullable()->comment('작업시작일시');
$table->timestamp('completed_at')->nullable()->comment('작업완료일시');
$table->timestamp('shipped_at')->nullable()->comment('출하일시');
$table->text('memo')->nullable()->comment('메모');
$table->boolean('is_active')->default(true)->comment('활성여부');
$table->unsignedBigInteger('created_by')->nullable()->comment('생성자');
$table->unsignedBigInteger('updated_by')->nullable()->comment('수정자');
$table->timestamps();
$table->softDeletes();
// Indexes
$table->unique(['tenant_id', 'work_order_no'], 'uq_work_orders_tenant_no');
$table->index(['tenant_id', 'status'], 'idx_work_orders_tenant_status');
$table->index(['tenant_id', 'process_type'], 'idx_work_orders_tenant_process');
$table->index(['tenant_id', 'assignee_id'], 'idx_work_orders_tenant_assignee');
$table->index(['tenant_id', 'scheduled_date'], 'idx_work_orders_tenant_scheduled');
$table->index(['tenant_id', 'is_active'], 'idx_work_orders_tenant_active');
});
}
public function down(): void
{
Schema::dropIfExists('work_orders');
}
};

View File

@@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* 작업지시 품목 테이블 (Work Order Items)
*/
public function up(): void
{
Schema::create('work_order_items', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('work_order_id')->comment('작업지시ID');
$table->unsignedBigInteger('item_id')->nullable()->comment('품목ID');
$table->string('item_name', 200)->comment('품목명');
$table->string('specification', 500)->nullable()->comment('규격');
$table->decimal('quantity', 12, 2)->default(1)->comment('수량');
$table->string('unit', 20)->nullable()->comment('단위');
$table->integer('sort_order')->default(0)->comment('정렬순서');
$table->timestamps();
// Foreign Keys
$table->foreign('work_order_id')
->references('id')
->on('work_orders')
->onDelete('cascade');
// Indexes
$table->index(['work_order_id', 'sort_order'], 'idx_work_order_items_order_sort');
});
}
public function down(): void
{
Schema::dropIfExists('work_order_items');
}
};

View File

@@ -0,0 +1,46 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* 작업지시 벤딩 상세 테이블 (Bending Process Details)
*/
public function up(): void
{
Schema::create('work_order_bending_details', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('work_order_id')->comment('작업지시ID');
// 벤딩 공정 세부 항목
$table->boolean('shaft_cutting')->default(false)->comment('샤프트 절단');
$table->boolean('bearing')->default(false)->comment('베어링');
$table->boolean('shaft_welding')->default(false)->comment('샤프트 용접');
$table->boolean('assembly')->default(false)->comment('조립');
$table->boolean('winder_welding')->default(false)->comment('와인더 용접');
$table->boolean('frame_assembly')->default(false)->comment('프레임 조립');
$table->boolean('bundle_assembly')->default(false)->comment('번들 조립');
$table->boolean('motor_assembly')->default(false)->comment('모터 조립');
$table->boolean('bracket_assembly')->default(false)->comment('브라켓 조립');
$table->timestamps();
// Foreign Keys
$table->foreign('work_order_id')
->references('id')
->on('work_orders')
->onDelete('cascade');
// Unique (1:1 관계)
$table->unique('work_order_id', 'uq_bending_details_work_order');
});
}
public function down(): void
{
Schema::dropIfExists('work_order_bending_details');
}
};

View File

@@ -0,0 +1,42 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* 작업지시 이슈 테이블 (Work Order Issues)
*/
public function up(): void
{
Schema::create('work_order_issues', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('work_order_id')->comment('작업지시ID');
$table->string('title', 200)->comment('이슈 제목');
$table->text('description')->nullable()->comment('이슈 설명');
$table->string('priority', 20)->default('medium')->comment('우선순위: high/medium/low');
$table->string('status', 30)->default('open')->comment('상태: open/in_progress/resolved');
$table->unsignedBigInteger('reported_by')->nullable()->comment('보고자ID');
$table->unsignedBigInteger('resolved_by')->nullable()->comment('해결자ID');
$table->timestamp('resolved_at')->nullable()->comment('해결일시');
$table->timestamps();
// Foreign Keys
$table->foreign('work_order_id')
->references('id')
->on('work_orders')
->onDelete('cascade');
// Indexes
$table->index(['work_order_id', 'status'], 'idx_work_order_issues_order_status');
$table->index(['work_order_id', 'priority'], 'idx_work_order_issues_order_priority');
});
}
public function down(): void
{
Schema::dropIfExists('work_order_issues');
}
};