@extends('layouts.app') @section('title', '문서 상세') @section('content')

문서 상세

@if($document->canEdit()) 수정 @endif @if($document->isPending()) @endif 성적서 목록
{{-- 문서 정보 --}}
{{-- 메인 컨텐츠 --}}
{{-- 기본 정보 --}}

기본 정보

문서번호
{{ $document->document_no }}
템플릿
{{ $document->template->name ?? '-' }}
제목
{{ $document->title }}
상태
{{ $document->status_label }}
작성자
{{ $document->creator->name ?? '-' }}
작성일
{{ $document->created_at?->format('Y-m-d H:i') ?? '-' }}
@if($document->updated_at && $document->updated_at->ne($document->created_at))
수정자
{{ $document->updater->name ?? '-' }}
수정일
{{ $document->updated_at?->format('Y-m-d H:i') ?? '-' }}
@endif
{{-- 기본 필드 데이터 --}} @if($document->template?->basicFields && $document->template->basicFields->count() > 0)

{{ $document->template->title ?? '문서 정보' }}

@foreach($document->template->basicFields as $field) @php $fieldKey = 'bf_' . $field->id; $fieldData = $document->data->where('field_key', $fieldKey)->first(); $value = $fieldData?->field_value ?? '-'; @endphp
{{ $field->label }}
{{ $value }}
@endforeach
@endif {{-- 섹션 데이터 (테이블) --}} @if($document->template?->sections && $document->template->sections->count() > 0) @foreach($document->template->sections as $section)

{{ $section->title }}

@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 {{ $section->title }} @endif {{-- 검사 데이터 테이블 (읽기 전용) --}} {{-- 정규화 형식: section_id + column_id + row_index + field_key 기반 조회 --}} @if($section->items->count() > 0 && $document->template->columns->count() > 0) @php // 데이터 조회 헬퍼: 정규화 형식 우선, 레거시 fallback $getData = function($sectionId, $colId, $rowIdx, $fieldKey) use ($document) { // 1차: 정규화 형식 (section_id + column_id + row_index + field_key) $record = $document->data ->where('section_id', $sectionId) ->where('column_id', $colId) ->where('row_index', $rowIdx) ->where('field_key', $fieldKey) ->first(); return $record?->field_value ?? ''; }; // 레거시 데이터 조회 헬퍼 (수입검사 호환: {itemId}_n{n}, {itemId}_result 등) $getLegacyData = function($itemId, $fieldKey) use ($document) { return $document->data->where('field_key', "{$itemId}_{$fieldKey}")->first()?->field_value ?? $document->data->where('field_key', $fieldKey)->first()?->field_value ?? ''; }; // 컬럼 → 섹션 아이템 매핑 (React normalizeLabel 로직 동기화) $normalizeLabel = fn($label) => preg_replace('/[①②③④⑤⑥⑦⑧⑨⑩\s]/u', '', trim($label)); $allItems = $section->items; $columnItemMap = []; foreach ($document->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; } } } // 기준치 해석: reference_attribute → work_order_item 치수 $resolveStandard = function($colId, $rowIndex) use ($columnItemMap, &$workOrderItems) { $sItem = $columnItemMap[$colId] ?? null; if (!$sItem) return ''; $woItem = $workOrderItems[$rowIndex] ?? null; // 1. reference_attribute → work_order_item 치수 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; } } // 2. standard_criteria $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; } // 3. standard 텍스트 return $sItem->standard ?? ''; }; @endphp
{{-- 테이블 헤더 --}} @foreach($document->template->columns as $col) @if($col->column_type === 'complex' && $col->sub_labels) @else @endif @endforeach {{-- 서브 라벨 행 --}} @if($document->template->columns->contains(fn($c) => $c->column_type === 'complex' && $c->sub_labels)) @foreach($document->template->columns as $col) @if($col->column_type === 'complex' && $col->sub_labels) @foreach($col->sub_labels as $subLabel) @endforeach @endif @endforeach @endif @php // 행 수 결정: workOrderItems 기반 (React effectiveWorkItems와 동일) // fallback: document_data의 max row_index + 1 $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++) @foreach($document->template->columns as $col) @if($col->column_type === 'complex' && $col->sub_labels) {{-- complex: sub_label 유형별 분리 (React inputIdx 로직 동기화) --}} @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) {{-- 기준치: document_data → work_order_item 치수 → item standard --}} @php $standardVal = $getData($section->id, $col->id, $rowIndex, 'standard'); if (!$standardVal) $standardVal = $resolveStandard($col->id, $rowIndex); @endphp @elseif($isOkNgSub) {{-- OK·NG sub_label --}} @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 @else {{-- 측정값 sub_label --}} @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 @endif @endforeach @elseif($col->column_type === 'select') {{-- 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 @elseif($col->column_type === 'check') {{-- check: 체크 결과 --}} @php $savedVal = $getData($section->id, $col->id, $rowIndex, 'value'); @endphp @elseif($col->column_type === 'measurement') {{-- measurement: 수치 데이터 --}} @php $savedVal = $getData($section->id, $col->id, $rowIndex, 'n1'); if (!$savedVal) $savedVal = $getData($section->id, $col->id, $rowIndex, 'value'); @endphp @else {{-- text: 일련번호 / 판정 / 기타 --}} @php $label = trim($col->label); $isNoCol = (str_contains(strtolower($label), 'no') && strlen($label) <= 4) || in_array($label, ['일렬번호', '일련번호', '번호', '순번']); $isJudgeCol = str_contains($label, '판정'); @endphp @if($isNoCol) @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 @else @php $savedVal = $getData($section->id, $col->id, $rowIndex, 'value'); if (!$savedVal) $savedVal = $getData($section->id, $col->id, $rowIndex, 'n1'); @endphp @endif @endif @endforeach @endfor
{{ $col->label }} {{ $col->label }}
{{ $subLabel }}
{{ $standardVal ?: '-' }} @if($savedVal) @if(strtolower($savedVal) === 'ok') OK @else NG @endif @else - @endif @if($isOkng && $savedVal) @if(strtolower($savedVal) === 'ok') OK @else NG @endif @else {{ $savedVal ?: '-' }} @endif @if($savedVal) @php $isPass = in_array(strtolower($savedVal), ['ok', '적합', '합격', 'pass', '적']); @endphp @if($isPass) @else @endif {{ $isPass ? '적합' : '부적합' }} @else - @endif @if(in_array(strtolower($savedVal), ['ok', 'pass', '적합', '합격'])) @elseif($savedVal) @else - @endif {{ $savedVal ?: '-' }}{{ $rowIndex + 1 }} @if(in_array(strtolower($savedVal ?? ''), ['ok', 'pass', '적합', '합격'])) @elseif($savedVal) @else - @endif {{ $savedVal ?: '-' }}
@endif {{-- 종합판정 / 비고 (마지막 섹션에만) --}} @if($loop->last) @php // React 형식: overall_result, remark (+ 레거시 호환: footer_judgement, footer_remark) $remarkVal = $document->data->where('field_key', 'remark')->first()?->field_value ?? $document->data->where('field_key', 'footer_remark')->first()?->field_value ?? ''; $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에서 계산 (React calculateOverallResult 동일 로직) 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 ? '합격' : ''); } } $isPass = in_array(strtolower($judgementVal), ['pass', 'ok', '적합', '합격']); @endphp
{{ $document->template->footer_remark_label ?? '비고' }}
{{ $remarkVal ?: '-' }}
{{ $document->template->footer_judgement_label ?? '종합판정' }}
@if($judgementVal) {{ $isPass ? '적합' : '부적합' }} @else - @endif
@endif
@endforeach @endif {{-- 첨부파일 --}} @if($document->attachments && $document->attachments->count() > 0)

첨부파일

    @foreach($document->attachments as $attachment)
  • {{ $attachment->file->original_name ?? '파일명 없음' }}

    {{ $attachment->type_label }} · {{ $attachment->file ? number_format($attachment->file->size / 1024, 1) . ' KB' : '-' }}

    @if($attachment->file) 다운로드 @endif
  • @endforeach
@endif
{{-- 사이드바 --}}
{{-- 결재 현황 --}}

결재 현황

@if($document->approvals && $document->approvals->count() > 0)
    @foreach($document->approvals as $approval)
  1. @if($approval->status === 'APPROVED') @elseif($approval->status === 'REJECTED') @else {{ $approval->step }} @endif

    {{ $approval->role }} ({{ $approval->status_label }})

    {{ $approval->user->name ?? '미지정' }}

    @if($approval->acted_at)

    {{ $approval->acted_at->format('Y-m-d H:i') }}

    @endif @if($approval->comment)

    {{ $approval->comment }}

    @endif
  2. @endforeach
@else

결재선이 설정되지 않았습니다.

@endif
{{-- 문서 이력 --}}

문서 이력

문서 이력 기능은 추후 구현 예정입니다.

{{-- 반려 사유 모달 --}} @if($document->isPending()) @endif @endsection @push('scripts') @endpush