- print.blade.php rendered_html 스냅샷 우선 출력 - bending-inspection-data, bending-worklog 파셜 추가 - documents/show.blade.php 개선 - DocumentTemplateSection 모델 보완 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
407 lines
23 KiB
PHP
407 lines
23 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('title', '문서 인쇄 - ' . $document->title)
|
|
|
|
@section('content')
|
|
<div class="max-w-7xl mx-auto">
|
|
{{-- 상단 버튼 --}}
|
|
<div class="flex justify-between items-center mb-4 print:hidden">
|
|
<div>
|
|
<h1 class="text-xl font-bold text-gray-800">{{ $document->title }}</h1>
|
|
<p class="text-sm text-gray-500">{{ $document->document_no }}</p>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<a href="{{ route('documents.show', $document->id) }}"
|
|
class="bg-gray-200 hover:bg-gray-300 text-gray-700 px-4 py-2 rounded-lg transition text-sm">
|
|
상세보기
|
|
</a>
|
|
<a href="{{ route('documents.index') }}"
|
|
class="bg-gray-200 hover:bg-gray-300 text-gray-700 px-4 py-2 rounded-lg transition text-sm">
|
|
목록
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- 성적서 본문 --}}
|
|
<div class="bg-white border border-gray-300 p-8 print:p-4 print:border-0" id="printArea">
|
|
|
|
{{-- HTML 스냅샷 우선 출력 (React에서 저장한 rendered_html) --}}
|
|
@if($document->rendered_html)
|
|
<div class="document-snapshot-container">
|
|
{!! $document->rendered_html !!}
|
|
</div>
|
|
@else
|
|
{{-- 레거시: 템플릿 기반 동적 렌더링 --}}
|
|
@php
|
|
$template = $document->template;
|
|
$hasComplexCol = $template->columns->contains(fn($c) => $c->column_type === 'complex' && $c->sub_labels);
|
|
|
|
// 정규화 데이터 조회 헬퍼
|
|
$getData = function($sectionId, $colId, $rowIdx, $fieldKey) use ($document) {
|
|
return $document->data
|
|
->where('section_id', $sectionId)
|
|
->where('column_id', $colId)
|
|
->where('row_index', $rowIdx)
|
|
->where('field_key', $fieldKey)
|
|
->first()?->field_value ?? '';
|
|
};
|
|
// 레거시 fallback 헬퍼
|
|
$getLegacyData = function($fieldKey) use ($document) {
|
|
return $document->data->where('field_key', $fieldKey)->first()?->field_value ?? '';
|
|
};
|
|
|
|
// 컬럼 → 섹션 아이템 매핑
|
|
$normalizeLabel = fn($label) => preg_replace('/[①②③④⑤⑥⑦⑧⑨⑩\s]/u', '', trim($label));
|
|
@endphp
|
|
|
|
{{-- 섹션별 검사 테이블 --}}
|
|
@if($template->sections && $template->sections->count() > 0)
|
|
@foreach($template->sections as $sectionIndex => $section)
|
|
|
|
{{-- 섹션 제목 --}}
|
|
@if($template->sections->count() > 1)
|
|
<div class="mb-2 {{ $sectionIndex > 0 ? 'mt-8' : '' }}">
|
|
<h3 class="text-sm font-semibold text-gray-700">{{ $section->title }}</h3>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- 검사 기준 이미지 --}}
|
|
@if($section->image_path)
|
|
@php
|
|
$sectionImgUrl = preg_match('/^\d+\//', $section->image_path)
|
|
? rtrim(config('app.api_url', 'http://api.sam.kr'), '/') . '/storage/tenants/' . $section->image_path
|
|
: asset('storage/' . $section->image_path);
|
|
@endphp
|
|
<div class="mb-4">
|
|
<img src="{{ $sectionImgUrl }}" alt="{{ $section->title }}" class="max-w-md h-auto border">
|
|
</div>
|
|
@endif
|
|
|
|
{{-- 검사 데이터 테이블 --}}
|
|
@if($section->items->count() > 0 && $template->columns->count() > 0)
|
|
@php
|
|
// 컬럼 → 섹션 아이템 매핑 + 기준치 해석
|
|
$allItems = $section->items;
|
|
$columnItemMap = [];
|
|
foreach ($template->columns as $col) {
|
|
$colKey = $normalizeLabel($col->label);
|
|
foreach ($allItems as $sItem) {
|
|
$itemKey = $normalizeLabel($sItem->item ?? $sItem->category ?? '');
|
|
if ($itemKey === $colKey) {
|
|
$columnItemMap[$col->id] = $sItem;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
$resolveStandard = function($colId, $rowIndex) use ($columnItemMap, &$workOrderItems) {
|
|
$sItem = $columnItemMap[$colId] ?? null;
|
|
if (!$sItem) return '';
|
|
$woItem = $workOrderItems[$rowIndex] ?? null;
|
|
if ($woItem) {
|
|
$fv = $sItem->field_values;
|
|
$refAttr = is_array($fv) ? ($fv['reference_attribute'] ?? null) : null;
|
|
if ($refAttr) {
|
|
$dimKey = $refAttr === 'length' ? 'width' : $refAttr;
|
|
$dimVal = $woItem->options[$dimKey] ?? null;
|
|
if ($dimVal) return (string) $dimVal;
|
|
}
|
|
}
|
|
$sc = $sItem->standard_criteria;
|
|
if ($sc) {
|
|
if (is_array($sc)) {
|
|
if (isset($sc['nominal'])) return (string) $sc['nominal'];
|
|
if (isset($sc['min'], $sc['max'])) return $sc['min'] . ' ~ ' . $sc['max'];
|
|
if (isset($sc['max'])) return '≤ ' . $sc['max'];
|
|
if (isset($sc['min'])) return '≥ ' . $sc['min'];
|
|
}
|
|
return (string) $sc;
|
|
}
|
|
return $sItem->standard ?? '';
|
|
};
|
|
@endphp
|
|
<table class="w-full border-collapse text-sm mb-2" style="border: 1px solid #333;">
|
|
{{-- 테이블 헤더 --}}
|
|
<thead>
|
|
<tr>
|
|
@foreach($template->columns as $col)
|
|
@if($col->column_type === 'complex' && $col->sub_labels)
|
|
<th colspan="{{ count($col->sub_labels) }}"
|
|
class="doc-th"
|
|
style="min-width: {{ $col->width ?: '80px' }}">
|
|
{{ $col->label }}
|
|
</th>
|
|
@else
|
|
<th rowspan="{{ $hasComplexCol ? 2 : 1 }}"
|
|
class="doc-th"
|
|
style="min-width: {{ $col->width ?: '60px' }}">
|
|
{{ $col->label }}
|
|
</th>
|
|
@endif
|
|
@endforeach
|
|
</tr>
|
|
{{-- 서브 라벨 행 (complex 컬럼) --}}
|
|
@if($hasComplexCol)
|
|
<tr>
|
|
@foreach($template->columns as $col)
|
|
@if($col->column_type === 'complex' && $col->sub_labels)
|
|
@foreach($col->sub_labels as $subLabel)
|
|
<th class="doc-th text-xs font-normal" style="min-width: 60px">
|
|
{{ $subLabel }}
|
|
</th>
|
|
@endforeach
|
|
@endif
|
|
@endforeach
|
|
</tr>
|
|
@endif
|
|
</thead>
|
|
|
|
{{-- 테이블 바디 (정규화 형식: section_id + column_id + row_index + field_key) --}}
|
|
<tbody>
|
|
@php
|
|
$rowCount = $workOrderItems->isNotEmpty()
|
|
? $workOrderItems->count()
|
|
: max(1, ($document->data->where('section_id', $section->id)->max('row_index') ?? 0) + 1);
|
|
@endphp
|
|
@for($rowIndex = 0; $rowIndex < $rowCount; $rowIndex++)
|
|
<tr>
|
|
@foreach($template->columns as $col)
|
|
@if($col->column_type === 'complex' && $col->sub_labels)
|
|
@php
|
|
$inputIdx = 0;
|
|
$mappedItem = $columnItemMap[$col->id] ?? null;
|
|
$isOkng = $mappedItem?->measurement_type === 'checkbox';
|
|
@endphp
|
|
@foreach($col->sub_labels as $subIndex => $subLabel)
|
|
@php
|
|
$sl = strtolower($subLabel);
|
|
$isStandardSub = str_contains($sl, '도면') || str_contains($sl, '기준');
|
|
$isOkNgSub = str_contains($sl, 'ok') || str_contains($sl, 'ng');
|
|
@endphp
|
|
|
|
@if($isStandardSub)
|
|
@php
|
|
$standardVal = $getData($section->id, $col->id, $rowIndex, 'standard');
|
|
if (!$standardVal) $standardVal = $resolveStandard($col->id, $rowIndex);
|
|
@endphp
|
|
<td class="doc-td text-center text-gray-500">
|
|
{{ $standardVal ?: '-' }}
|
|
</td>
|
|
@elseif($isOkNgSub)
|
|
@php
|
|
$n = $inputIdx + 1;
|
|
$okVal = $getData($section->id, $col->id, $rowIndex, "n{$n}_ok");
|
|
$ngVal = $getData($section->id, $col->id, $rowIndex, "n{$n}_ng");
|
|
$savedVal = $okVal === 'OK' ? 'OK' : ($ngVal === 'NG' ? 'NG' : '');
|
|
if (!$savedVal) {
|
|
$valFallback = $getData($section->id, $col->id, $rowIndex, 'value');
|
|
if (in_array(strtolower($valFallback), ['ok', 'pass', '적합', '합격'])) $savedVal = 'OK';
|
|
elseif (in_array(strtolower($valFallback), ['ng', 'fail', '부적합'])) $savedVal = 'NG';
|
|
}
|
|
$inputIdx++;
|
|
@endphp
|
|
<td class="doc-td text-center">
|
|
@if($savedVal)
|
|
<span class="{{ strtolower($savedVal) === 'ok' ? 'text-blue-700' : 'text-red-600' }} font-bold text-sm">{{ strtoupper($savedVal) }}</span>
|
|
@else
|
|
-
|
|
@endif
|
|
</td>
|
|
@else
|
|
@php
|
|
$n = $inputIdx + 1;
|
|
if ($isOkng) {
|
|
$okVal = $getData($section->id, $col->id, $rowIndex, "n{$n}_ok");
|
|
$ngVal = $getData($section->id, $col->id, $rowIndex, "n{$n}_ng");
|
|
$savedVal = $okVal === 'OK' ? 'OK' : ($ngVal === 'NG' ? 'NG' : '');
|
|
if (!$savedVal) {
|
|
$valFallback = $getData($section->id, $col->id, $rowIndex, 'value');
|
|
if (in_array(strtolower($valFallback), ['ok', 'pass', '적합', '합격'])) $savedVal = 'OK';
|
|
elseif (in_array(strtolower($valFallback), ['ng', 'fail', '부적합'])) $savedVal = 'NG';
|
|
}
|
|
} else {
|
|
$savedVal = $getData($section->id, $col->id, $rowIndex, "n{$n}");
|
|
if (!$savedVal) $savedVal = $getData($section->id, $col->id, $rowIndex, 'value');
|
|
}
|
|
$inputIdx++;
|
|
@endphp
|
|
<td class="doc-td font-mono text-center">
|
|
@if($isOkng && $savedVal)
|
|
<span class="{{ strtolower($savedVal) === 'ok' ? 'text-blue-700' : 'text-red-600' }} font-bold text-sm">{{ strtoupper($savedVal) }}</span>
|
|
@else
|
|
{{ $savedVal ?: '-' }}
|
|
@endif
|
|
</td>
|
|
@endif
|
|
@endforeach
|
|
|
|
@elseif($col->column_type === 'select')
|
|
@php
|
|
$savedVal = $getData($section->id, $col->id, $rowIndex, 'value');
|
|
if (!$savedVal) {
|
|
$rowJudgment = $document->data->where('field_key', 'row_judgment')->first()?->field_value ?? '';
|
|
if (in_array(strtolower($rowJudgment), ['pass', 'ok', '적합'])) $savedVal = 'OK';
|
|
elseif (in_array(strtolower($rowJudgment), ['fail', 'ng', '부적합'])) $savedVal = 'NG';
|
|
}
|
|
@endphp
|
|
<td class="doc-td text-center">
|
|
@if($savedVal)
|
|
<span class="{{ in_array(strtolower($savedVal), ['적합', '합격', 'ok', 'pass', '적']) ? 'text-blue-700' : 'text-red-600' }} font-bold text-sm">
|
|
{{ in_array(strtolower($savedVal), ['ok', 'pass', '적합', '합격', '적']) ? '적합' : '부적합' }}
|
|
</span>
|
|
@else
|
|
-
|
|
@endif
|
|
</td>
|
|
|
|
@elseif($col->column_type === 'check')
|
|
@php
|
|
$savedVal = $getData($section->id, $col->id, $rowIndex, 'value');
|
|
@endphp
|
|
<td class="doc-td text-center">
|
|
@if(in_array(strtolower($savedVal), ['ok', 'pass', '적합', '합격']))
|
|
<span class="text-blue-700 font-bold text-sm">OK</span>
|
|
@elseif($savedVal)
|
|
<span class="text-red-600 font-bold text-sm">NG</span>
|
|
@else
|
|
-
|
|
@endif
|
|
</td>
|
|
|
|
@elseif($col->column_type === 'measurement')
|
|
@php
|
|
$savedVal = $getData($section->id, $col->id, $rowIndex, 'n1');
|
|
if (!$savedVal) $savedVal = $getData($section->id, $col->id, $rowIndex, 'value');
|
|
@endphp
|
|
<td class="doc-td font-mono text-center">
|
|
{{ $savedVal ?: '-' }}
|
|
</td>
|
|
|
|
@else
|
|
@php
|
|
$label = trim($col->label);
|
|
$isNoCol = (str_contains(strtolower($label), 'no') && strlen($label) <= 4)
|
|
|| in_array($label, ['일렬번호', '일련번호', '번호', '순번']);
|
|
$isJudgeCol = str_contains($label, '판정');
|
|
@endphp
|
|
@if($isNoCol)
|
|
<td class="doc-td text-center">{{ $rowIndex + 1 }}</td>
|
|
@elseif($isJudgeCol)
|
|
@php
|
|
$savedVal = $getData($section->id, $col->id, $rowIndex, 'value');
|
|
if (!$savedVal) {
|
|
$rowJudgment = $document->data->where('field_key', 'row_judgment')->first()?->field_value ?? '';
|
|
if (in_array(strtolower($rowJudgment), ['pass', 'ok', '적합'])) $savedVal = 'OK';
|
|
elseif (in_array(strtolower($rowJudgment), ['fail', 'ng', '부적합'])) $savedVal = 'NG';
|
|
}
|
|
@endphp
|
|
<td class="doc-td text-center">
|
|
@if(in_array(strtolower($savedVal ?? ''), ['ok', 'pass', '적합', '합격']))
|
|
<span class="text-blue-700 font-bold text-sm">OK</span>
|
|
@elseif($savedVal)
|
|
<span class="text-red-600 font-bold text-sm">NG</span>
|
|
@else
|
|
-
|
|
@endif
|
|
</td>
|
|
@else
|
|
@php
|
|
$savedVal = $getData($section->id, $col->id, $rowIndex, 'value');
|
|
if (!$savedVal) $savedVal = $getData($section->id, $col->id, $rowIndex, 'n1');
|
|
@endphp
|
|
<td class="doc-td text-center">
|
|
{{ $savedVal ?: '-' }}
|
|
</td>
|
|
@endif
|
|
@endif
|
|
@endforeach
|
|
</tr>
|
|
@endfor
|
|
</tbody>
|
|
</table>
|
|
@endif
|
|
|
|
@endforeach
|
|
@endif
|
|
|
|
{{-- 종합판정 --}}
|
|
@if($template->footer_judgement_label)
|
|
<div class="mt-6 flex justify-end">
|
|
<table class="border-collapse text-sm" style="border: 1px solid #333; min-width: 200px;">
|
|
<tr>
|
|
<td class="doc-th font-semibold text-center" style="width: 100px;">
|
|
{{ $template->footer_judgement_label ?? '종합판정' }}
|
|
</td>
|
|
<td class="doc-td text-center" style="min-width: 100px;">
|
|
@php
|
|
$judgementVal = $document->data->where('field_key', 'overall_result')->first()?->field_value
|
|
?? $document->data->where('field_key', 'footer_judgement')->first()?->field_value
|
|
?? '';
|
|
// fallback: overall_result 없으면 row_judgment에서 계산
|
|
if (!$judgementVal) {
|
|
$rowJudgments = $document->data->where('field_key', 'row_judgment')->pluck('field_value');
|
|
if ($rowJudgments->isNotEmpty()) {
|
|
$hasFail = $rowJudgments->contains(fn($v) => in_array(strtolower($v), ['fail', '부', '부적합', '불합격']));
|
|
$allPass = $rowJudgments->every(fn($v) => in_array(strtolower($v), ['pass', '적', '적합', '합격']));
|
|
$judgementVal = $hasFail ? '불합격' : ($allPass ? '합격' : '');
|
|
}
|
|
}
|
|
@endphp
|
|
@if($judgementVal)
|
|
<span class="{{ in_array($judgementVal, ['적합', '합격']) ? 'text-blue-700' : 'text-red-600' }} font-semibold">
|
|
{{ $judgementVal }}
|
|
</span>
|
|
@else
|
|
<span class="text-gray-400">미완료</span>
|
|
@endif
|
|
</td>
|
|
</tr>
|
|
@php
|
|
$remarkVal = $document->data->where('field_key', 'remark')->first()?->field_value
|
|
?? $document->data->where('field_key', 'footer_remark')->first()?->field_value
|
|
?? '';
|
|
@endphp
|
|
@if($remarkVal)
|
|
<tr>
|
|
<td class="doc-th font-semibold text-center">
|
|
{{ $template->footer_remark_label ?? '비고' }}
|
|
</td>
|
|
<td class="doc-td">{{ $remarkVal }}</td>
|
|
</tr>
|
|
@endif
|
|
</table>
|
|
</div>
|
|
@endif
|
|
@endif {{-- 스냅샷 vs 레거시 분기 끝 --}}
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
/* 성적서 테이블 공통 스타일 */
|
|
.doc-th {
|
|
border: 1px solid #555;
|
|
padding: 6px 8px;
|
|
background-color: #f9fafb;
|
|
font-weight: 500;
|
|
font-size: 0.8125rem;
|
|
text-align: center;
|
|
color: #374151;
|
|
white-space: nowrap;
|
|
}
|
|
.doc-td {
|
|
border: 1px solid #999;
|
|
padding: 5px 8px;
|
|
font-size: 0.8125rem;
|
|
color: #1f2937;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
/* 인쇄 스타일 */
|
|
@media print {
|
|
body { background: white; }
|
|
.doc-th { background-color: #f3f4f6 !important; -webkit-print-color-adjust: exact; }
|
|
.doc-td { border-color: #666 !important; }
|
|
}
|
|
</style>
|
|
@endsection
|