tenantId(); $perPage = (int) ($params['per_page'] ?? 20); $q = trim((string) ($params['q'] ?? '')); $year = $params['year'] ?? null; $quarter = $params['quarter'] ?? null; $confirmStatus = $params['confirm_status'] ?? null; $query = PerformanceReport::query() ->where('performance_reports.tenant_id', $tenantId) ->with(['qualityDocument.client', 'qualityDocument.locations', 'confirmer:id,name']); if ($q !== '') { $query->whereHas('qualityDocument', function ($qq) use ($q) { $qq->where('quality_doc_number', 'like', "%{$q}%") ->orWhere('site_name', 'like', "%{$q}%"); }); } if ($year !== null) { $query->where('year', $year); } if ($quarter !== null) { $query->where('quarter', $quarter); } if ($confirmStatus !== null) { $query->where('confirmation_status', $confirmStatus); } $query->orderByDesc('performance_reports.id'); $paginated = $query->paginate($perPage); $transformedData = $paginated->getCollection()->map(fn ($report) => $this->transformToFrontend($report)); return [ 'items' => $transformedData, 'current_page' => $paginated->currentPage(), 'last_page' => $paginated->lastPage(), 'per_page' => $paginated->perPage(), 'total' => $paginated->total(), ]; } /** * 통계 조회 */ public function stats(array $params = []): array { $tenantId = $this->tenantId(); $query = PerformanceReport::where('performance_reports.tenant_id', $tenantId); if (! empty($params['year'])) { $query->where('performance_reports.year', $params['year']); } if (! empty($params['quarter'])) { $query->where('performance_reports.quarter', $params['quarter']); } $counts = (clone $query) ->select('confirmation_status', DB::raw('count(*) as count')) ->groupBy('confirmation_status') ->pluck('count', 'confirmation_status') ->toArray(); $totalLocations = (clone $query) ->join('quality_documents', 'quality_documents.id', '=', 'performance_reports.quality_document_id') ->join('quality_document_locations', 'quality_document_locations.quality_document_id', '=', 'quality_documents.id') ->count('quality_document_locations.id'); return [ 'total_count' => array_sum($counts), 'confirmed_count' => $counts[PerformanceReport::STATUS_CONFIRMED] ?? 0, 'unconfirmed_count' => $counts[PerformanceReport::STATUS_UNCONFIRMED] ?? 0, 'total_locations' => $totalLocations, ]; } /** * 일괄 확정 */ public function confirm(array $ids) { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); return DB::transaction(function () use ($ids, $tenantId, $userId) { $reports = PerformanceReport::where('tenant_id', $tenantId) ->whereIn('id', $ids) ->with(['qualityDocument']) ->get(); $errors = []; foreach ($reports as $report) { if ($report->isConfirmed() || $report->isReported()) { continue; } // 필수정보 검증 $requiredInfo = $this->qualityDocumentService->calculateRequiredInfo($report->qualityDocument); if ($requiredInfo !== '완료') { $errors[] = [ 'id' => $report->id, 'quality_doc_number' => $report->qualityDocument->quality_doc_number, 'reason' => $requiredInfo, ]; continue; } $report->update([ 'confirmation_status' => PerformanceReport::STATUS_CONFIRMED, 'confirmed_date' => now()->toDateString(), 'confirmed_by' => $userId, 'updated_by' => $userId, ]); } if (! empty($errors)) { throw new BadRequestHttpException(json_encode([ 'message' => __('error.quality.confirm_failed'), 'errors' => $errors, ])); } return ['confirmed_count' => count($ids) - count($errors)]; }); } /** * 일괄 확정 해제 */ public function unconfirm(array $ids) { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); return DB::transaction(function () use ($ids, $tenantId, $userId) { PerformanceReport::where('tenant_id', $tenantId) ->whereIn('id', $ids) ->where('confirmation_status', PerformanceReport::STATUS_CONFIRMED) ->update([ 'confirmation_status' => PerformanceReport::STATUS_UNCONFIRMED, 'confirmed_date' => null, 'confirmed_by' => null, 'updated_by' => $userId, ]); return ['unconfirmed_count' => count($ids)]; }); } /** * 일괄 메모 업데이트 */ public function updateMemo(array $ids, string $memo) { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); PerformanceReport::where('tenant_id', $tenantId) ->whereIn('id', $ids) ->update([ 'memo' => $memo, 'updated_by' => $userId, ]); return ['updated_count' => count($ids)]; } /** * 누락체크 (출고완료 but 제품검사 미등록) */ public function missing(array $params): array { $tenantId = $this->tenantId(); // 품질관리서가 등록된 수주 ID $registeredOrderIds = DB::table('quality_document_orders') ->join('quality_documents', 'quality_documents.id', '=', 'quality_document_orders.quality_document_id') ->where('quality_documents.tenant_id', $tenantId) ->pluck('quality_document_orders.order_id'); // 출고완료 상태이지만 품질관리서 미등록 수주 $query = DB::table('orders') ->where('tenant_id', $tenantId) ->whereNotIn('id', $registeredOrderIds) ->where('status_code', 'SHIPPED'); // TODO: 출고완료 상태 추가 시 상수 확인 if (! empty($params['year'])) { $query->whereYear('created_at', $params['year']); } if (! empty($params['quarter'])) { $quarter = (int) $params['quarter']; $startMonth = ($quarter - 1) * 3 + 1; $endMonth = $quarter * 3; $query->whereMonth('created_at', '>=', $startMonth) ->whereMonth('created_at', '<=', $endMonth); } return $query->orderByDesc('id') ->limit(100) ->get() ->map(fn ($order) => [ 'id' => $order->id, 'order_number' => $order->order_no ?? '', 'site_name' => $order->site_name ?? '', 'client' => '', // 별도 조인 필요 'delivery_date' => $order->delivery_date ?? '', ]) ->toArray(); } /** * DB → 프론트엔드 변환 */ private function transformToFrontend(PerformanceReport $report): array { $doc = $report->qualityDocument; return [ 'id' => $report->id, 'quality_doc_number' => $doc?->quality_doc_number ?? '', 'created_date' => $report->created_at?->format('Y-m-d') ?? '', 'site_name' => $doc?->site_name ?? '', 'client' => $doc?->client?->name ?? '', 'location_count' => $doc?->locations?->count() ?? 0, 'required_info' => $doc ? $this->qualityDocumentService->calculateRequiredInfo($doc) : '', 'confirm_status' => $report->confirmation_status === PerformanceReport::STATUS_CONFIRMED ? 'confirmed' : 'unconfirmed', 'confirm_date' => $report->confirmed_date?->format('Y-m-d'), 'memo' => $report->memo ?? '', 'year' => $report->year, 'quarter' => $report->quarter, ]; } }