tenantId(); $page = (int) ($params['page'] ?? 1); $size = (int) ($params['size'] ?? 20); $q = trim((string) ($params['q'] ?? '')); $processType = $params['process_type'] ?? null; $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 = WorkResult::query() ->where('tenant_id', $tenantId) ->with([ 'workOrder:id,work_order_no', 'worker:id,name', ]); // 검색어 if ($q !== '') { $query->where(function ($qq) use ($q) { $qq->where('lot_no', 'like', "%{$q}%") ->orWhere('product_name', 'like', "%{$q}%") ->orWhereHas('workOrder', fn ($wo) => $wo->where('work_order_no', 'like', "%{$q}%")); }); } // 공정유형 필터 if ($processType !== null) { $query->where('process_type', $processType); } // 작업지시 필터 if ($workOrderId !== null) { $query->where('work_order_id', $workOrderId); } // 작업자 필터 if ($workerId !== null) { $query->where('worker_id', $workerId); } // 작업일 범위 if ($workDateFrom !== null) { $query->where('work_date', '>=', $workDateFrom); } if ($workDateTo !== null) { $query->where('work_date', '<=', $workDateTo); } // 검사 완료 필터 if ($isInspected !== null) { $query->where('is_inspected', $isInspected); } // 포장 완료 필터 if ($isPackaged !== null) { $query->where('is_packaged', $isPackaged); } $query->orderByDesc('work_date')->orderByDesc('created_at'); return $query->paginate($size, ['*'], 'page', $page); } /** * 통계 조회 */ public function stats(array $params = []): array { $tenantId = $this->tenantId(); $workDateFrom = $params['work_date_from'] ?? null; $workDateTo = $params['work_date_to'] ?? null; $processType = $params['process_type'] ?? null; $query = WorkResult::where('tenant_id', $tenantId); // 작업일 범위 if ($workDateFrom !== null) { $query->where('work_date', '>=', $workDateFrom); } if ($workDateTo !== null) { $query->where('work_date', '<=', $workDateTo); } // 공정유형 필터 if ($processType !== null) { $query->where('process_type', $processType); } $totals = $query->select([ DB::raw('SUM(production_qty) as total_production'), DB::raw('SUM(good_qty) as total_good'), DB::raw('SUM(defect_qty) as total_defect'), ])->first(); $totalProduction = (int) ($totals->total_production ?? 0); $totalGood = (int) ($totals->total_good ?? 0); $totalDefect = (int) ($totals->total_defect ?? 0); $defectRate = $totalProduction > 0 ? round(($totalDefect / $totalProduction) * 100, 1) : 0; return [ 'total_production' => $totalProduction, 'total_good' => $totalGood, 'total_defect' => $totalDefect, 'defect_rate' => $defectRate, ]; } /** * 단건 조회 */ public function show(int $id) { $tenantId = $this->tenantId(); $workResult = WorkResult::where('tenant_id', $tenantId) ->with([ 'workOrder:id,work_order_no,project_name,status', 'workOrderItem:id,item_name,specification,quantity', 'worker:id,name', ]) ->find($id); if (! $workResult) { throw new NotFoundHttpException(__('error.not_found')); } return $workResult; } /** * 생성 */ public function store(array $data) { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); return DB::transaction(function () use ($data, $tenantId, $userId) { $data['tenant_id'] = $tenantId; $data['created_by'] = $userId; $data['updated_by'] = $userId; // 양품수량 자동 계산 (입력 안 된 경우) if (! isset($data['good_qty'])) { $data['good_qty'] = max(0, ($data['production_qty'] ?? 0) - ($data['defect_qty'] ?? 0)); } // 작업지시 정보로 자동 채움 if (! empty($data['work_order_id'])) { $workOrder = WorkOrder::find($data['work_order_id']); if ($workOrder) { $data['process_type'] = $data['process_type'] ?? $workOrder->process_type; } } return WorkResult::create($data); }); } /** * 수정 */ public function update(int $id, array $data) { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); $workResult = WorkResult::where('tenant_id', $tenantId)->find($id); if (! $workResult) { throw new NotFoundHttpException(__('error.not_found')); } return DB::transaction(function () use ($workResult, $data, $userId) { $data['updated_by'] = $userId; // 양품수량 재계산 (생산수량 또는 불량수량 변경 시) if (isset($data['production_qty']) || isset($data['defect_qty'])) { $productionQty = $data['production_qty'] ?? $workResult->production_qty; $defectQty = $data['defect_qty'] ?? $workResult->defect_qty; if (! isset($data['good_qty'])) { $data['good_qty'] = max(0, $productionQty - $defectQty); } } $workResult->update($data); return $workResult->fresh([ 'workOrder:id,work_order_no', 'worker:id,name', ]); }); } /** * 삭제 */ public function destroy(int $id): void { $tenantId = $this->tenantId(); $workResult = WorkResult::where('tenant_id', $tenantId)->find($id); if (! $workResult) { throw new NotFoundHttpException(__('error.not_found')); } $workResult->delete(); } /** * 검사 상태 토글 */ public function toggleInspection(int $id) { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); $workResult = WorkResult::where('tenant_id', $tenantId)->find($id); if (! $workResult) { throw new NotFoundHttpException(__('error.not_found')); } $workResult->update([ 'is_inspected' => ! $workResult->is_inspected, 'updated_by' => $userId, ]); return $workResult->fresh(); } /** * 포장 상태 토글 */ public function togglePackaging(int $id) { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); $workResult = WorkResult::where('tenant_id', $tenantId)->find($id); if (! $workResult) { throw new NotFoundHttpException(__('error.not_found')); } $workResult->update([ 'is_packaged' => ! $workResult->is_packaged, 'updated_by' => $userId, ]); return $workResult->fresh(); } }