diff --git a/app/Http/Requests/WorkOrder/WorkOrderStoreRequest.php b/app/Http/Requests/WorkOrder/WorkOrderStoreRequest.php index 0fe7813..a61aa2c 100644 --- a/app/Http/Requests/WorkOrder/WorkOrderStoreRequest.php +++ b/app/Http/Requests/WorkOrder/WorkOrderStoreRequest.php @@ -4,7 +4,6 @@ use App\Models\Production\WorkOrder; use Illuminate\Foundation\Http\FormRequest; -use Illuminate\Validation\Rule; class WorkOrderStoreRequest extends FormRequest { @@ -19,8 +18,8 @@ public function rules(): array // 기본 정보 'sales_order_id' => 'nullable|integer|exists:orders,id', 'project_name' => 'nullable|string|max:200', - 'process_type' => ['required', Rule::in(WorkOrder::PROCESS_TYPES)], - 'status' => ['nullable', Rule::in(WorkOrder::STATUSES)], + 'process_id' => 'required|integer|exists:processes,id', + 'status' => ['nullable', 'in:'.implode(',', WorkOrder::STATUSES)], 'assignee_id' => 'nullable|integer|exists:users,id', 'team_id' => 'nullable|integer|exists:departments,id', 'scheduled_date' => 'nullable|date', @@ -35,7 +34,7 @@ public function rules(): array 'items.*.quantity' => 'nullable|numeric|min:0', 'items.*.unit' => 'nullable|string|max:20', - // 벤딩 상세 (process_type이 bending인 경우) + // 벤딩 상세 (절곡 공정인 경우) 'bending_detail' => 'nullable|array', 'bending_detail.shaft_cutting' => 'nullable|boolean', 'bending_detail.bearing' => 'nullable|boolean', @@ -52,8 +51,8 @@ public function rules(): array public function messages(): array { return [ - 'process_type.required' => __('validation.required', ['attribute' => '공정유형']), - 'process_type.in' => __('validation.in', ['attribute' => '공정유형']), + 'process_id.required' => __('validation.required', ['attribute' => '공정']), + 'process_id.exists' => __('validation.exists', ['attribute' => '공정']), 'items.*.item_name.required' => __('validation.required', ['attribute' => '품목명']), ]; } diff --git a/app/Http/Requests/WorkOrder/WorkOrderUpdateRequest.php b/app/Http/Requests/WorkOrder/WorkOrderUpdateRequest.php index 1c5e5e4..2297e94 100644 --- a/app/Http/Requests/WorkOrder/WorkOrderUpdateRequest.php +++ b/app/Http/Requests/WorkOrder/WorkOrderUpdateRequest.php @@ -4,7 +4,6 @@ use App\Models\Production\WorkOrder; use Illuminate\Foundation\Http\FormRequest; -use Illuminate\Validation\Rule; class WorkOrderUpdateRequest extends FormRequest { @@ -19,8 +18,8 @@ public function rules(): array // 기본 정보 'sales_order_id' => 'nullable|integer|exists:orders,id', 'project_name' => 'nullable|string|max:200', - 'process_type' => ['nullable', Rule::in(WorkOrder::PROCESS_TYPES)], - 'status' => ['nullable', Rule::in(WorkOrder::STATUSES)], + 'process_id' => 'nullable|integer|exists:processes,id', + 'status' => ['nullable', 'in:'.implode(',', WorkOrder::STATUSES)], 'assignee_id' => 'nullable|integer|exists:users,id', 'team_id' => 'nullable|integer|exists:departments,id', 'scheduled_date' => 'nullable|date', diff --git a/app/Models/Process.php b/app/Models/Process.php index 7eecf78..cb769d2 100644 --- a/app/Models/Process.php +++ b/app/Models/Process.php @@ -67,4 +67,12 @@ public function items(): BelongsToMany ->withTimestamps() ->orderByPivot('priority'); } + + /** + * 작업지시들 + */ + public function workOrders(): HasMany + { + return $this->hasMany(Production\WorkOrder::class); + } } diff --git a/app/Models/Production/WorkOrder.php b/app/Models/Production/WorkOrder.php index 9100f1c..9b7b509 100644 --- a/app/Models/Production/WorkOrder.php +++ b/app/Models/Production/WorkOrder.php @@ -4,6 +4,7 @@ use App\Models\Members\User; use App\Models\Orders\Order; +use App\Models\Process; use App\Models\Tenants\Department; use App\Traits\BelongsToTenant; use App\Traits\ModelTrait; @@ -28,8 +29,8 @@ class WorkOrder extends Model 'tenant_id', 'work_order_no', 'sales_order_id', + 'process_id', 'project_name', - 'process_type', 'status', 'assignee_id', 'team_id', @@ -60,14 +61,18 @@ class WorkOrder extends Model // ────────────────────────────────────────────────────────────── /** - * 공정 유형 + * @deprecated 공정유형은 processes 테이블의 process_name으로 관리됨 + * 하위 호환성을 위해 유지. process_id FK 사용 권장 */ public const PROCESS_SCREEN = 'screen'; + /** @deprecated process_id FK 사용 권장 */ public const PROCESS_SLAT = 'slat'; + /** @deprecated process_id FK 사용 권장 */ public const PROCESS_BENDING = 'bending'; + /** @deprecated process_id FK 사용 권장 */ public const PROCESS_TYPES = [ self::PROCESS_SCREEN, self::PROCESS_SLAT, @@ -123,6 +128,14 @@ public function salesOrder(): BelongsTo return $this->belongsTo(Order::class, 'sales_order_id'); } + /** + * 공정 (processes 테이블 참조) + */ + public function process(): BelongsTo + { + return $this->belongsTo(Process::class); + } + /** * 담당자 (주 담당자 - 하위 호환) */ @@ -208,11 +221,21 @@ public function scopeStatus($query, string $status) } /** - * 공정유형별 필터 + * 공정 ID별 필터 */ - public function scopeProcessType($query, string $type) + public function scopeForProcess($query, int $processId) { - return $query->where('process_type', $type); + return $query->where('process_id', $processId); + } + + /** + * 공정명으로 필터 (process_name 기준) + */ + public function scopeForProcessName($query, string $processName) + { + return $query->whereHas('process', function ($q) use ($processName) { + $q->where('process_name', $processName); + }); } /** @@ -279,7 +302,15 @@ public function scopeScheduledBetween($query, $from, $to) */ public function isBending(): bool { - return $this->process_type === self::PROCESS_BENDING; + return $this->process && $this->process->process_name === '절곡'; + } + + /** + * 특정 공정명인지 확인 + */ + public function isProcessName(string $processName): bool + { + return $this->process && $this->process->process_name === $processName; } /** diff --git a/app/Services/OrderService.php b/app/Services/OrderService.php index 80aa562..f7ab3e5 100644 --- a/app/Services/OrderService.php +++ b/app/Services/OrderService.php @@ -398,7 +398,7 @@ public function createFromQuote(int $quoteId, array $data = []) } /** - * 생산지시 생성 + * 생산지시 생성 (공정별 작업지시 다중 생성) */ public function createProductionOrder(int $orderId, array $data) { @@ -428,26 +428,43 @@ public function createProductionOrder(int $orderId, array $data) throw new BadRequestHttpException(__('error.order.production_order_already_exists')); } - return DB::transaction(function () use ($order, $data, $tenantId, $userId) { - // 작업지시번호 생성 - $workOrderNo = $this->generateWorkOrderNo($tenantId); + // process_ids 배열 또는 단일 process_id 처리 + $processIds = $data['process_ids'] ?? []; + if (empty($processIds) && ! empty($data['process_id'])) { + $processIds = [$data['process_id']]; + } - // 작업지시 생성 - $workOrder = WorkOrder::create([ - 'tenant_id' => $tenantId, - 'work_order_no' => $workOrderNo, - 'sales_order_id' => $order->id, - 'project_name' => $order->site_name ?? $order->client_name, - 'process_type' => $data['process_type'] ?? WorkOrder::PROCESS_SCREEN, - 'status' => WorkOrder::STATUS_PENDING, - 'assignee_id' => $data['assignee_id'] ?? null, - 'team_id' => $data['team_id'] ?? null, - 'scheduled_date' => $data['scheduled_date'] ?? $order->delivery_date, - 'memo' => $data['memo'] ?? null, - 'is_active' => true, - 'created_by' => $userId, - 'updated_by' => $userId, - ]); + // 공정이 없으면 null로 하나만 생성 + if (empty($processIds)) { + $processIds = [null]; + } + + return DB::transaction(function () use ($order, $data, $tenantId, $userId, $processIds) { + $workOrders = []; + + foreach ($processIds as $processId) { + // 작업지시번호 생성 + $workOrderNo = $this->generateWorkOrderNo($tenantId); + + // 작업지시 생성 + $workOrder = WorkOrder::create([ + 'tenant_id' => $tenantId, + 'work_order_no' => $workOrderNo, + 'sales_order_id' => $order->id, + 'project_name' => $order->site_name ?? $order->client_name, + 'process_id' => $processId, + 'status' => WorkOrder::STATUS_PENDING, + 'assignee_id' => $data['assignee_id'] ?? null, + 'team_id' => $data['team_id'] ?? null, + 'scheduled_date' => $data['scheduled_date'] ?? $order->delivery_date, + 'memo' => $data['memo'] ?? null, + 'is_active' => true, + 'created_by' => $userId, + 'updated_by' => $userId, + ]); + + $workOrders[] = $workOrder->load(['assignee:id,name', 'team:id,name', 'process:id,process_name,process_code']); + } // 수주 상태를 IN_PROGRESS로 변경 $order->status_code = Order::STATUS_IN_PROGRESS; @@ -455,7 +472,8 @@ public function createProductionOrder(int $orderId, array $data) $order->save(); return [ - 'work_order' => $workOrder->load(['assignee:id,name', 'team:id,name']), + 'work_orders' => $workOrders, + 'work_order' => $workOrders[0] ?? null, // 하위 호환성 'order' => $order->load(['client:id,name', 'items']), ]; });