result 데이터를 기반으로 작업실적 조회/수정 */ class WorkResultService extends Service { /** * 목록 조회 (검색/필터링/페이징) * * 완료된 WorkOrderItem에서 result 데이터가 있는 항목만 조회 */ public function index(array $params) { $tenantId = $this->tenantId(); $page = (int) ($params['page'] ?? 1); $size = (int) ($params['size'] ?? 20); $q = trim((string) ($params['q'] ?? '')); $workOrderId = $params['work_order_id'] ?? null; $workerId = $params['worker_id'] ?? null; $workDateFrom = $params['work_date_from'] ?? null; $workDateTo = $params['work_date_to'] ?? null; $isInspected = isset($params['is_inspected']) ? filter_var($params['is_inspected'], FILTER_VALIDATE_BOOLEAN) : null; $isPackaged = isset($params['is_packaged']) ? filter_var($params['is_packaged'], FILTER_VALIDATE_BOOLEAN) : null; $query = WorkOrderItem::query() ->where('tenant_id', $tenantId) ->where('status', WorkOrderItem::STATUS_COMPLETED) ->whereNotNull('options->result') ->with([ 'workOrder:id,work_order_no,project_name,process_id,completed_at', 'workOrder.process:id,process_name,process_code', ]); // 검색어 (로트번호, 품목명, 작업지시번호) if ($q !== '') { $query->where(function ($qq) use ($q) { $qq->where('options->result->lot_no', 'like', "%{$q}%") ->orWhere('item_name', 'like', "%{$q}%") ->orWhereHas('workOrder', fn ($wo) => $wo->where('work_order_no', 'like', "%{$q}%")); }); } // 작업지시 필터 if ($workOrderId !== null) { $query->where('work_order_id', $workOrderId); } // 작업자 필터 if ($workerId !== null) { $query->where('options->result->worker_id', $workerId); } // 작업일 범위 (completed_at 기준) if ($workDateFrom !== null) { $query->where('options->result->completed_at', '>=', $workDateFrom); } if ($workDateTo !== null) { $query->where('options->result->completed_at', '<=', $workDateTo.' 23:59:59'); } // 검사 완료 필터 if ($isInspected !== null) { $query->where('options->result->is_inspected', $isInspected); } // 포장 완료 필터 if ($isPackaged !== null) { $query->where('options->result->is_packaged', $isPackaged); } // 최신 완료순 정렬 $query->orderByDesc('options->result->completed_at')->orderByDesc('id'); $paginated = $query->paginate($size, ['*'], 'page', $page); // worker_id로 작업자 이름 조회하여 추가 $workerIds = $paginated->getCollection() ->map(fn ($item) => $item->options['result']['worker_id'] ?? null) ->filter() ->unique() ->values() ->toArray(); $workers = []; if (! empty($workerIds)) { $workers = User::whereIn('id', $workerIds) ->pluck('name', 'id') ->toArray(); } // 각 아이템에 worker_name 추가 $paginated->getCollection()->transform(function ($item) use ($workers) { $workerId = $item->options['result']['worker_id'] ?? null; $item->worker_name = $workerId ? ($workers[$workerId] ?? null) : null; return $item; }); return $paginated; } /** * 통계 조회 */ public function stats(array $params = []): array { $tenantId = $this->tenantId(); $workDateFrom = $params['work_date_from'] ?? null; $workDateTo = $params['work_date_to'] ?? null; $query = WorkOrderItem::where('tenant_id', $tenantId) ->where('status', WorkOrderItem::STATUS_COMPLETED) ->whereNotNull('options->result'); // 작업일 범위 if ($workDateFrom !== null) { $query->where('options->result->completed_at', '>=', $workDateFrom); } if ($workDateTo !== null) { $query->where('options->result->completed_at', '<=', $workDateTo.' 23:59:59'); } // JSON에서 집계 (MySQL/MariaDB 기준) $items = $query->get(); $totalProduction = 0; $totalGood = 0; $totalDefect = 0; foreach ($items as $item) { $result = $item->options['result'] ?? []; $goodQty = (float) ($result['good_qty'] ?? 0); $defectQty = (float) ($result['defect_qty'] ?? 0); $totalGood += $goodQty; $totalDefect += $defectQty; $totalProduction += ($goodQty + $defectQty); } $defectRate = $totalProduction > 0 ? round(($totalDefect / $totalProduction) * 100, 1) : 0; return [ 'total_production' => (int) $totalProduction, 'total_good' => (int) $totalGood, 'total_defect' => (int) $totalDefect, 'defect_rate' => $defectRate, ]; } /** * 단건 조회 */ public function show(int $id) { $tenantId = $this->tenantId(); $item = WorkOrderItem::where('tenant_id', $tenantId) ->where('status', WorkOrderItem::STATUS_COMPLETED) ->whereNotNull('options->result') ->with([ 'workOrder:id,work_order_no,project_name,process_id,completed_at', 'workOrder.process:id,process_name,process_code', ]) ->find($id); if (! $item) { throw new NotFoundHttpException(__('error.not_found')); } // worker_name 추가 $workerId = $item->options['result']['worker_id'] ?? null; if ($workerId) { $item->worker_name = User::where('id', $workerId)->value('name'); } else { $item->worker_name = null; } return $item; } /** * 작업실적 수정 (양품/불량 수량 등) */ public function update(int $id, array $data) { $tenantId = $this->tenantId(); $item = WorkOrderItem::where('tenant_id', $tenantId) ->where('status', WorkOrderItem::STATUS_COMPLETED) ->whereNotNull('options->result') ->find($id); if (! $item) { throw new NotFoundHttpException(__('error.not_found')); } return DB::transaction(function () use ($item, $data) { $options = $item->options ?? []; $result = $options['result'] ?? []; // 수정 가능한 필드만 업데이트 $allowedFields = ['good_qty', 'defect_qty', 'lot_no', 'is_inspected', 'is_packaged', 'memo']; foreach ($allowedFields as $field) { if (array_key_exists($field, $data)) { $result[$field] = $data[$field]; } } // 불량률 재계산 $totalQty = ($result['good_qty'] ?? 0) + ($result['defect_qty'] ?? 0); $result['defect_rate'] = $totalQty > 0 ? round(($result['defect_qty'] / $totalQty) * 100, 2) : 0; $options['result'] = $result; $item->options = $options; $item->save(); return $item->fresh([ 'workOrder:id,work_order_no,project_name,process_id', 'workOrder.process:id,process_name,process_code', ]); }); } /** * 검사 상태 토글 */ public function toggleInspection(int $id) { $tenantId = $this->tenantId(); $item = WorkOrderItem::where('tenant_id', $tenantId) ->where('status', WorkOrderItem::STATUS_COMPLETED) ->whereNotNull('options->result') ->find($id); if (! $item) { throw new NotFoundHttpException(__('error.not_found')); } $options = $item->options ?? []; $result = $options['result'] ?? []; $result['is_inspected'] = ! ($result['is_inspected'] ?? false); $options['result'] = $result; $item->options = $options; $item->save(); return $item->fresh(); } /** * 포장 상태 토글 */ public function togglePackaging(int $id) { $tenantId = $this->tenantId(); $item = WorkOrderItem::where('tenant_id', $tenantId) ->where('status', WorkOrderItem::STATUS_COMPLETED) ->whereNotNull('options->result') ->find($id); if (! $item) { throw new NotFoundHttpException(__('error.not_found')); } $options = $item->options ?? []; $result = $options['result'] ?? []; $result['is_packaged'] = ! ($result['is_packaged'] ?? false); $options['result'] = $result; $item->options = $options; $item->save(); return $item->fresh(); } }