diff --git a/app/Services/DocumentService.php b/app/Services/DocumentService.php index 84373d7..b4e614c 100644 --- a/app/Services/DocumentService.php +++ b/app/Services/DocumentService.php @@ -445,6 +445,195 @@ public function cancel(int $id): Document }); } + // ========================================================================= + // FQC 일괄생성 (제품검사) + // ========================================================================= + + /** + * 수주 개소별 제품검사 문서 일괄생성 + * + * Order의 OrderItem 수만큼 Document를 DRAFT 상태로 생성. + * 기본필드(납품명, 제품명, 발주처, LOT NO, 로트크기) 자동매핑. + */ + public function bulkCreateFqc(array $data): array + { + $tenantId = $this->tenantId(); + $userId = $this->apiUserId(); + $templateId = $data['template_id']; + $orderId = $data['order_id']; + + // 템플릿 존재 확인 + $template = DocumentTemplate::where('tenant_id', $tenantId) + ->where('is_active', true) + ->findOrFail($templateId); + + // 수주 + 개소 조회 + $order = \App\Models\Orders\Order::where('tenant_id', $tenantId) + ->with('items') + ->findOrFail($orderId); + + if ($order->items->isEmpty()) { + throw new BadRequestHttpException(__('error.document.no_order_items')); + } + + // 이미 생성된 문서 확인 (중복 방지) + $existingLinkableIds = Document::where('tenant_id', $tenantId) + ->where('template_id', $templateId) + ->where('linkable_type', \App\Models\Orders\OrderItem::class) + ->whereIn('linkable_id', $order->items->pluck('id')) + ->pluck('linkable_id') + ->toArray(); + + $itemsToCreate = $order->items->reject(function ($item) use ($existingLinkableIds) { + return in_array($item->id, $existingLinkableIds); + }); + + if ($itemsToCreate->isEmpty()) { + throw new BadRequestHttpException(__('error.document.already_created')); + } + + // 템플릿의 기본필드 로드 (bf_{id} 형식으로 저장하기 위해) + $template = DocumentTemplate::with('basicFields')->find($templateId); + + return DB::transaction(function () use ($itemsToCreate, $order, $templateId, $tenantId, $userId, $template) { + $documents = []; + + foreach ($itemsToCreate as $orderItem) { + // 문서번호 생성 + $documentNo = $this->generateDocumentNo($tenantId, $templateId); + + // 개소 식별 문자열 + $locationLabel = trim("{$orderItem->floor_code}-{$orderItem->symbol_code}"); + $specLabel = $orderItem->specification ?? ''; + $titleSuffix = $specLabel ? "{$locationLabel} ({$specLabel})" : $locationLabel; + + // Document 생성 + $document = Document::create([ + 'tenant_id' => $tenantId, + 'template_id' => $templateId, + 'document_no' => $documentNo, + 'title' => "제품검사 - {$titleSuffix}", + 'status' => Document::STATUS_DRAFT, + 'linkable_type' => \App\Models\Orders\OrderItem::class, + 'linkable_id' => $orderItem->id, + 'created_by' => $userId, + 'updated_by' => $userId, + ]); + + // 기본필드 자동매핑 (bf_{id} 형식, mng show.blade.php 호환) + $resolveMap = [ + 'product_name' => $orderItem->item_name, + 'client' => $order->client_name, + 'lot_no' => $order->order_no, + 'lot_size' => '1 EA', + 'site_name' => $order->site_name ?? '', + ]; + + if ($template && $template->basicFields) { + foreach ($template->basicFields as $field) { + $value = $resolveMap[$field->field_key] ?? ''; + + // field_key가 없는 필드는 라벨 매칭 + if (! $value && ! $field->field_key) { + if (str_contains($field->label, '납품')) { + $value = $order->site_name ?? $order->order_no; + } + } + + if ($value) { + DocumentData::create([ + 'document_id' => $document->id, + 'section_id' => null, + 'column_id' => null, + 'row_index' => 0, + 'field_key' => "bf_{$field->id}", + 'field_value' => (string) $value, + ]); + } + } + } + + $documents[] = $document; + } + + return [ + 'created_count' => count($documents), + 'skipped_count' => count($existingLinkableIds ?? []), + 'total_items' => count($documents) + count($existingLinkableIds ?? []), + 'documents' => collect($documents)->map(fn ($doc) => [ + 'id' => $doc->id, + 'document_no' => $doc->document_no, + 'title' => $doc->title, + 'status' => $doc->status, + 'linkable_id' => $doc->linkable_id, + ])->toArray(), + ]; + }); + } + + /** + * 수주 개소별 FQC 진행현황 조회 + */ + public function fqcStatus(int $orderId, int $templateId): array + { + $tenantId = $this->tenantId(); + + $order = \App\Models\Orders\Order::where('tenant_id', $tenantId) + ->with('items') + ->findOrFail($orderId); + + // 해당 수주의 FQC 문서 조회 + $documents = Document::where('tenant_id', $tenantId) + ->where('template_id', $templateId) + ->where('linkable_type', \App\Models\Orders\OrderItem::class) + ->whereIn('linkable_id', $order->items->pluck('id')) + ->with('data') + ->get() + ->keyBy('linkable_id'); + + $items = $order->items->map(function ($orderItem) use ($documents) { + $doc = $documents->get($orderItem->id); + + // 종합판정 값 추출 + $judgement = null; + if ($doc) { + $judgementData = $doc->data->firstWhere('field_key', 'footer_judgement'); + $judgement = $judgementData?->field_value; + } + + return [ + 'order_item_id' => $orderItem->id, + 'floor_code' => $orderItem->floor_code, + 'symbol_code' => $orderItem->symbol_code, + 'specification' => $orderItem->specification, + 'item_name' => $orderItem->item_name, + 'document_id' => $doc?->id, + 'document_no' => $doc?->document_no, + 'status' => $doc?->status ?? 'NONE', + 'judgement' => $judgement, + ]; + }); + + // 통계 + $total = $items->count(); + $created = $items->where('status', '!=', 'NONE')->count(); + $approved = $items->where('status', 'APPROVED')->count(); + $passed = $items->where('judgement', '합격')->count(); + $failed = $items->where('judgement', '불합격')->count(); + + return [ + 'order_id' => $orderId, + 'order_no' => $order->order_no, + 'total' => $total, + 'created' => $created, + 'approved' => $approved, + 'passed' => $passed, + 'failed' => $failed, + 'pending' => $total - $created, + 'items' => $items->toArray(), + ]; + } + // ========================================================================= // Resolve/Upsert (React 연동용) // =========================================================================