header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('documents.index')); } $tenantId = session('selected_tenant_id'); // 템플릿 목록 (필터용) $templates = $tenantId ? DocumentTemplate::where(function ($q) use ($tenantId) { $q->whereNull('tenant_id')->orWhere('tenant_id', $tenantId); })->where('is_active', true)->orderBy('name')->get() : collect(); return view('documents.index', [ 'templates' => $templates, 'statuses' => Document::STATUS_LABELS, ]); } /** * 문서 생성 페이지 */ public function create(Request $request): View|Response { if ($request->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('documents.create', $request->query())); } $tenantId = session('selected_tenant_id'); $templateId = $request->query('template_id'); // 템플릿 목록 $templates = $tenantId ? DocumentTemplate::where(function ($q) use ($tenantId) { $q->whereNull('tenant_id')->orWhere('tenant_id', $tenantId); })->where('is_active', true)->orderBy('name')->get() : collect(); // 선택된 템플릿 $template = $templateId ? DocumentTemplate::with(['approvalLines', 'basicFields', 'sections.items', 'columns', 'sectionFields', 'links.linkValues'])->find($templateId) : null; return view('documents.edit', [ 'document' => null, 'template' => $template, 'templates' => $templates, 'isCreate' => true, 'linkedItemSpecs' => $template ? $this->getLinkedItemSpecs($template) : [], ]); } /** * 문서 수정 페이지 */ public function edit(int $id): View|Response { if (request()->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('documents.edit', $id)); } $tenantId = session('selected_tenant_id'); $document = Document::with([ 'template.approvalLines', 'template.basicFields', 'template.sections.items', 'template.columns', 'template.sectionFields', 'template.links.linkValues', 'approvals.user', 'data', 'attachments.file', 'creator', ])->where('tenant_id', $tenantId)->findOrFail($id); // 템플릿 목록 (변경용) $templates = $tenantId ? DocumentTemplate::where(function ($q) use ($tenantId) { $q->whereNull('tenant_id')->orWhere('tenant_id', $tenantId); })->where('is_active', true)->orderBy('name')->get() : collect(); // 기본정보 bf_ 자동 backfill (show 안 거치고 바로 edit 진입 대비) $this->resolveAndBackfillBasicFields($document); return view('documents.edit', [ 'document' => $document, 'template' => $document->template, 'templates' => $templates, 'isCreate' => false, 'linkedItemSpecs' => $this->getLinkedItemSpecs($document->template), ]); } /** * 문서 인쇄용 화면 (성적서 양식) */ public function print(int $id): View { $tenantId = session('selected_tenant_id'); $document = Document::with([ 'template.approvalLines', 'template.basicFields', 'template.sections.items', 'template.columns', 'template.sectionFields', 'template.links.linkValues', 'approvals.user', 'data', 'creator', ])->where('tenant_id', $tenantId)->findOrFail($id); // 연결된 작업지시서의 work_order_items 로드 $workOrderItems = collect(); if ($document->linkable_type === 'work_order' && $document->linkable_id) { $workOrderItems = DB::table('work_order_items') ->where('work_order_id', $document->linkable_id) ->orderBy('sort_order') ->get() ->map(function ($item) { $item->options = json_decode($item->options, true) ?? []; return $item; }); } // 기본정보 bf_ 자동 backfill $this->resolveAndBackfillBasicFields($document); return view('documents.print', [ 'document' => $document, 'workOrderItems' => $workOrderItems, ]); } /** * 문서 상세 페이지 (읽기 전용) */ public function show(int $id): View { $tenantId = session('selected_tenant_id'); $document = Document::with([ 'template.approvalLines', 'template.basicFields', 'template.sections.items', 'template.columns', 'template.sectionFields', 'template.links.linkValues', 'approvals.user', 'data', 'attachments.file', 'creator', 'updater', ])->where('tenant_id', $tenantId)->findOrFail($id); // 연결된 작업지시서의 work_order_items 로드 $workOrderItems = collect(); if ($document->linkable_type === 'work_order' && $document->linkable_id) { $workOrderItems = DB::table('work_order_items') ->where('work_order_id', $document->linkable_id) ->orderBy('sort_order') ->get() ->map(function ($item) { $item->options = json_decode($item->options, true) ?? []; return $item; }); } // 기본정보 bf_ 자동 backfill $this->resolveAndBackfillBasicFields($document); return view('documents.show', [ 'document' => $document, 'workOrderItems' => $workOrderItems, ]); } /** * 기본정보(bf_) 레코드가 없으면 원본 데이터에서 resolve → document_data에 저장 * React resolveFieldValue와 동일 매핑 로직 */ private function resolveAndBackfillBasicFields(Document $document): void { $basicFields = $document->template?->basicFields; if (! $basicFields || $basicFields->isEmpty()) { return; } // bf_ 레코드가 하나라도 있으면 이미 저장된 것 → skip $existingBfCount = $document->data ->filter(fn ($d) => str_starts_with($d->field_key, 'bf_')) ->count(); if ($existingBfCount > 0) { return; } // 원본 데이터 로드: work_order + order if ($document->linkable_type !== 'work_order' || ! $document->linkable_id) { return; } $workOrder = DB::table('work_orders')->find($document->linkable_id); if (! $workOrder) { return; } $workOrderItems = DB::table('work_order_items') ->where('work_order_id', $workOrder->id) ->orderBy('sort_order') ->get() ->map(function ($item) { $item->options = json_decode($item->options, true) ?? []; return $item; }); $order = $workOrder->sales_order_id ? DB::table('orders')->find($workOrder->sales_order_id) : null; // 검사자 정보: work_order_items[0].options.inspection_data.inspected_by $firstItem = $workOrderItems->first(); $inspectionData = $firstItem?->options['inspection_data'] ?? []; $inspectedBy = $inspectionData['inspected_by'] ?? null; $inspectedAt = $inspectionData['inspected_at'] ?? null; $inspectorName = $inspectedBy ? DB::table('users')->where('id', $inspectedBy)->value('name') : null; // field_key → 값 매핑 $resolveMap = [ 'product_name' => $firstItem?->item_name ?? '', 'specification' => $firstItem?->specification ?? '', 'lot_no' => $order?->order_no ?? '', 'lot_size' => $workOrderItems->count().' 개소', 'client' => $order?->client_name ?? '', 'site_name' => $workOrder->project_name ?? '', 'inspection_date' => $inspectedAt ? substr($inspectedAt, 0, 10) : now()->format('Y-m-d'), 'inspector' => $inspectorName ?? '', ]; // document_data에 bf_ 레코드 저장 $records = []; foreach ($basicFields as $field) { $value = $resolveMap[$field->field_key] ?? $field->default_value ?? ''; if ($value === '') { continue; } $records[] = [ 'document_id' => $document->id, 'section_id' => null, 'column_id' => null, 'row_index' => 0, 'field_key' => 'bf_'.$field->id, 'field_value' => (string) $value, 'created_at' => now(), 'updated_at' => now(), ]; } if (! empty($records)) { DocumentData::insert($records); // 메모리의 data relation도 갱신 $document->load('data'); } } /** * 템플릿에 연결된 품목들의 규격 정보 (thickness, width, length) 조회 */ private function getLinkedItemSpecs(DocumentTemplate $template): array { $specs = []; if (! $template->relationLoaded('links')) { $template->load('links.linkValues'); } foreach ($template->links as $link) { if ($link->source_table !== 'items') { continue; } foreach ($link->linkValues as $lv) { $item = Item::find($lv->linkable_id); if (! $item) { continue; } $attrs = $item->attributes ?? []; if (isset($attrs['thickness']) || isset($attrs['width']) || isset($attrs['length'])) { $specs[] = [ 'id' => $item->id, 'name' => $item->name, 'thickness' => $attrs['thickness'] ?? null, 'width' => $attrs['width'] ?? null, 'length' => $attrs['length'] ?? null, ]; } } } return $specs; } }