Files
sam-manage/resources/views/documents/print.blade.php
권혁성 3e1d1ffc33 feat: [문서인쇄] 스냅샷 출력 + 절곡 전용 렌더링
- 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>
2026-03-07 03:05:07 +09:00

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