diff --git a/app/Http/Controllers/Api/Admin/DocumentApiController.php b/app/Http/Controllers/Api/Admin/DocumentApiController.php
index 774e8d07..470e4b04 100644
--- a/app/Http/Controllers/Api/Admin/DocumentApiController.php
+++ b/app/Http/Controllers/Api/Admin/DocumentApiController.php
@@ -20,10 +20,17 @@ public function index(Request $request): JsonResponse
{
$tenantId = session('selected_tenant_id');
+ // 슈퍼관리자 휴지통 조회
+ $showTrashed = $request->filled('trashed') && auth()->user()?->is_super_admin;
+
$query = Document::with(['template', 'creator'])
->where('tenant_id', $tenantId)
->orderBy('created_at', 'desc');
+ if ($showTrashed) {
+ $query->onlyTrashed();
+ }
+
// 상태 필터
if ($request->filled('status')) {
$query->where('status', $request->status);
@@ -207,14 +214,7 @@ public function destroy(int $id): JsonResponse
$document = Document::where('tenant_id', $tenantId)->findOrFail($id);
- // 작성중 상태에서만 삭제 가능
- if ($document->status !== Document::STATUS_DRAFT) {
- return response()->json([
- 'success' => false,
- 'message' => '작성중 상태의 문서만 삭제할 수 있습니다.',
- ], 422);
- }
-
+ $document->update(['deleted_by' => auth()->id()]);
$document->delete();
return response()->json([
@@ -223,6 +223,57 @@ public function destroy(int $id): JsonResponse
]);
}
+ /**
+ * 문서 영구삭제 (슈퍼관리자 전용)
+ */
+ public function forceDestroy(int $id): JsonResponse
+ {
+ if (!auth()->user()?->is_super_admin) {
+ return response()->json([
+ 'success' => false,
+ 'message' => '슈퍼관리자만 영구 삭제할 수 있습니다.',
+ ], 403);
+ }
+
+ $tenantId = session('selected_tenant_id');
+
+ $document = Document::withTrashed()->where('tenant_id', $tenantId)->findOrFail($id);
+
+ // 관련 데이터도 영구삭제
+ $document->data()->delete();
+ $document->approvals()->delete();
+ $document->forceDelete();
+
+ return response()->json([
+ 'success' => true,
+ 'message' => '문서가 영구 삭제되었습니다.',
+ ]);
+ }
+
+ /**
+ * 삭제된 문서 복원 (슈퍼관리자 전용)
+ */
+ public function restore(int $id): JsonResponse
+ {
+ if (!auth()->user()?->is_super_admin) {
+ return response()->json([
+ 'success' => false,
+ 'message' => '슈퍼관리자만 복원할 수 있습니다.',
+ ], 403);
+ }
+
+ $tenantId = session('selected_tenant_id');
+
+ $document = Document::onlyTrashed()->where('tenant_id', $tenantId)->findOrFail($id);
+ $document->update(['deleted_by' => null]);
+ $document->restore();
+
+ return response()->json([
+ 'success' => true,
+ 'message' => '문서가 복원되었습니다.',
+ ]);
+ }
+
/**
* 결재 제출 (DRAFT → PENDING)
*/
diff --git a/app/Http/Controllers/DocumentController.php b/app/Http/Controllers/DocumentController.php
index 33cebf7a..15970add 100644
--- a/app/Http/Controllers/DocumentController.php
+++ b/app/Http/Controllers/DocumentController.php
@@ -4,8 +4,10 @@
use App\Models\Documents\Document;
use App\Models\DocumentTemplate;
+use App\Models\Items\Item;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
+use Illuminate\Support\Facades\DB;
use Illuminate\View\View;
class DocumentController extends Controller
@@ -63,6 +65,7 @@ public function create(Request $request): View|Response
'template' => $template,
'templates' => $templates,
'isCreate' => true,
+ 'linkedItemSpecs' => $template ? $this->getLinkedItemSpecs($template) : [],
]);
}
@@ -102,6 +105,7 @@ public function edit(int $id): View|Response
'template' => $document->template,
'templates' => $templates,
'isCreate' => false,
+ 'linkedItemSpecs' => $this->getLinkedItemSpecs($document->template),
]);
}
@@ -154,4 +158,42 @@ public function show(int $id): View
'document' => $document,
]);
}
+
+ /**
+ * 템플릿에 연결된 품목들의 규격 정보 (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;
+ }
}
diff --git a/app/Http/Controllers/DocumentTemplateController.php b/app/Http/Controllers/DocumentTemplateController.php
index 91ab5999..7f1a67f6 100644
--- a/app/Http/Controllers/DocumentTemplateController.php
+++ b/app/Http/Controllers/DocumentTemplateController.php
@@ -141,19 +141,22 @@ private function prepareTemplateData(DocumentTemplate $template): array
'title' => $s->title,
'image_path' => $s->image_path,
'items' => $s->items->map(function ($i) {
+ $fv = $i->field_values ?? [];
+
return [
'id' => $i->id,
- 'category' => $i->category,
- 'item' => $i->item,
- 'standard' => $i->standard,
- 'tolerance' => $i->tolerance,
- 'standard_criteria' => $i->standard_criteria,
- 'method' => $i->method,
- 'measurement_type' => $i->measurement_type,
- 'frequency_n' => $i->frequency_n,
- 'frequency_c' => $i->frequency_c,
- 'frequency' => $i->frequency,
- 'regulation' => $i->regulation,
+ 'category' => $fv['category'] ?? $i->category,
+ 'item' => $fv['item'] ?? $i->item,
+ 'standard' => $fv['standard'] ?? $i->standard,
+ 'tolerance' => $fv['tolerance'] ?? $i->tolerance,
+ 'standard_criteria' => $fv['standard_criteria'] ?? $i->standard_criteria,
+ 'method' => $fv['method'] ?? $i->method,
+ 'measurement_type' => $fv['measurement_type'] ?? $i->measurement_type,
+ 'frequency_n' => $fv['frequency_n'] ?? $i->frequency_n,
+ 'frequency_c' => $fv['frequency_c'] ?? $i->frequency_c,
+ 'frequency' => $fv['frequency'] ?? $i->frequency,
+ 'regulation' => $fv['regulation'] ?? $i->regulation,
+ 'field_values' => $fv,
];
})->toArray(),
];
diff --git a/app/Models/Items/Item.php b/app/Models/Items/Item.php
index 8d352cd0..d7c8a540 100644
--- a/app/Models/Items/Item.php
+++ b/app/Models/Items/Item.php
@@ -21,5 +21,6 @@ class Item extends Model
protected $casts = [
'is_active' => 'boolean',
+ 'attributes' => 'array',
];
}
diff --git a/resources/views/document-templates/edit.blade.php b/resources/views/document-templates/edit.blade.php
index c062dc48..0a87a9e0 100644
--- a/resources/views/document-templates/edit.blade.php
+++ b/resources/views/document-templates/edit.blade.php
@@ -67,8 +67,8 @@ class="tab-btn px-6 py-4 text-sm font-medium border-b-2 border-transparent text-
-
-
+
+
-
-
-
-
+
-
-
-
+
+
+
+ 또는
+
+
-
-
+
+
+
+
+
+
+
+
+
+
-
연결 설정이 없습니다.
@@ -357,7 +374,7 @@ function initBasicInfo() {
onCategoryChange(categorySelect.value, true);
// 동적 연결 UI 렌더링
- renderTemplateLinks();
+ populateLinkSourceTable();
// 프리셋 select 초기화
initPresetSelect();
// 필드 설정 렌더링
@@ -399,7 +416,7 @@ function onCategoryChange(value, isInit = false) {
}
// 연결 UI 렌더링
- renderTemplateLinks();
+ populateLinkSourceTable();
}
// ===== 결재라인 단계명 변경 핸들러 =====
@@ -807,7 +824,7 @@ function updateToleranceProp(sectionId, itemId, prop, value) {
function renderToleranceInput(sectionId, itemId, tol) {
const tolType = tol?.type || '';
const selectHtml = `
`;
} else if (tolType === 'asymmetric') {
fieldsHtml = `
+
+ class="px-1 py-0.5 border border-gray-200 rounded text-xs" style="width:36px">
-
+ class="px-1 py-0.5 border border-gray-200 rounded text-xs" style="width:36px">
`;
} else if (tolType === 'range') {
fieldsHtml = `
+ class="px-1 py-0.5 border border-gray-200 rounded text-xs" style="width:36px">
~
+ class="px-1 py-0.5 border border-gray-200 rounded text-xs" style="width:36px">
`;
} else if (tolType === 'limit') {
fieldsHtml = `
+ class="px-1 py-0.5 border border-gray-200 rounded text-xs" style="width:48px">
`;
}
@@ -957,11 +974,11 @@ class="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm font-medium">
${section.items.length > 0 ? `
-
+
${templateState.section_fields.map(f => `
- | ${escapeHtml(f.label)} |
+ ${escapeHtml(f.label)} |
`).join('')}
|
@@ -970,7 +987,7 @@ class="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm font-medium">
${section.items.map(item => `
${templateState.section_fields.map(f => `
- |
+ |
${renderDynamicFieldInput(f, section.id, item)}
|
`).join('')}
@@ -1151,7 +1168,7 @@ function saveTemplate() {
sections: templateState.sections,
columns: templateState.columns,
section_fields: templateState.section_fields,
- template_links: templateState.template_links
+ template_links: templateState.template_links.filter(l => l.source_table)
};
const url = templateState.id
@@ -1193,6 +1210,22 @@ function openPreviewModal() {
return m ? m.name : (code || '-');
};
+ // field_values를 top-level 프로퍼티로 풀어서 미리보기 호환
+ const normalizedSections = templateState.sections.map(s => ({
+ ...s,
+ items: (s.items || []).map(item => {
+ const merged = { ...item };
+ if (item.field_values) {
+ Object.entries(item.field_values).forEach(([k, v]) => {
+ if (v !== null && v !== undefined) {
+ merged[k] = v;
+ }
+ });
+ }
+ return merged;
+ })
+ }));
+
const data = {
title: document.getElementById('title').value,
company_name: document.getElementById('company_name').value,
@@ -1201,7 +1234,7 @@ function openPreviewModal() {
footer_judgement_options: templateState.footer_judgement_options,
approval_lines: templateState.approval_lines,
basic_fields: templateState.basic_fields,
- sections: templateState.sections,
+ sections: normalizedSections,
columns: templateState.columns,
methodResolver: getMethodName
};
@@ -1437,7 +1470,7 @@ function applyPresetData(preset) {
}));
}
renderSectionFields();
- renderTemplateLinks();
+ populateLinkSourceTable();
renderSections();
}
@@ -1508,104 +1541,32 @@ class="w-3 h-3">
`).join('');
}
- // ===== 동적 연결 설정 =====
- function addTemplateLink() {
- templateState.template_links.push({
- id: generateId(),
- link_key: '',
- label: '',
- link_type: 'single',
- source_table: '',
- search_params: null,
- display_fields: null,
- is_required: false,
- values: []
- });
- renderTemplateLinks();
- }
-
- function removeTemplateLink(id) {
- templateState.template_links = templateState.template_links.filter(l => l.id != id);
- renderTemplateLinks();
- }
-
- function updateTemplateLink(id, key, value) {
- const link = templateState.template_links.find(l => l.id == id);
- if (link) link[key] = value;
- }
-
- function renderTemplateLinks() {
- const container = document.getElementById('template-links-container');
- const emptyMsg = document.getElementById('template-links-empty');
- if (!container) return;
-
- if (templateState.template_links.length === 0) {
- container.innerHTML = '';
- if (emptyMsg) emptyMsg.classList.remove('hidden');
- return;
- }
- if (emptyMsg) emptyMsg.classList.add('hidden');
-
- container.innerHTML = templateState.template_links.map((link, idx) => `
-
-
-
-
- 또는
- t.key === link.source_table) ? 'disabled' : `value="${escapeHtml(link.source_table || '')}"`}>
-
-
- ${link.source_table ? `
-
-
-
- ${(link.values || []).map(v => `
-
- ${escapeHtml(v.display_text || 'ID: ' + (v.linkable_id || v.id))}
-
-
- `).join('')}
-
-
- ` : ''}
-
- `).join('');
-
- // 소스 테이블 드롭다운 옵션 채우기
- populateSourceTableSelects();
- }
-
- // ===== 소스 테이블 관련 함수 =====
+ // ===== 연결 설정 (소스테이블 1개 + 체크박스 다건 검색) =====
let sourceTableOptions = [];
- let linkSearchTimers = {};
+ let linkSearchTimer = null;
+ let checkedItems = new Map(); // id → {id, title, subtitle}
+
+ function getMainLink() {
+ if (templateState.template_links.length === 0) return null;
+ return templateState.template_links[0];
+ }
+
+ function ensureMainLink() {
+ if (templateState.template_links.length === 0) {
+ templateState.template_links.push({
+ id: generateId(),
+ link_key: '',
+ label: '',
+ link_type: 'multiple',
+ source_table: '',
+ search_params: null,
+ display_fields: null,
+ is_required: false,
+ values: []
+ });
+ }
+ return templateState.template_links[0];
+ }
async function loadSourceTableOptions() {
try {
@@ -1619,69 +1580,139 @@ class="w-full px-2 py-1 border border-gray-300 rounded text-xs">
}
}
- function populateSourceTableSelects() {
- document.querySelectorAll('.source-table-select').forEach(select => {
- const linkId = select.dataset.linkId;
- const link = templateState.template_links.find(l => l.id == linkId);
- const currentValue = link ? link.source_table : '';
+ function populateLinkSourceTable() {
+ const select = document.getElementById('link-source-table');
+ if (!select) return;
- // 기존 옵션 유지하되 동적 옵션 추가
- let html = '';
- sourceTableOptions.forEach(t => {
- html += ``;
- });
- select.innerHTML = html;
+ const link = getMainLink();
+ const currentValue = link ? link.source_table : '';
- // 직접 입력 필드 상태 설정
- const input = select.closest('.flex').querySelector('.source-table-input');
- if (input) {
- if (currentValue && sourceTableOptions.find(t => t.key === currentValue)) {
- input.disabled = true;
- input.value = '';
- } else {
- input.disabled = false;
- input.value = currentValue || '';
- }
- }
+ let html = '';
+ sourceTableOptions.forEach(t => {
+ html += ``;
});
- }
+ select.innerHTML = html;
- function onSourceTableChange(linkId, value) {
- const link = templateState.template_links.find(l => l.id == linkId);
- if (!link) return;
-
- link.source_table = value;
- link.values = []; // 소스 테이블 변경 시 기존 연결 값 초기화
-
- // 소스 테이블 메타 정보로 display_fields 자동 설정
- const tableInfo = sourceTableOptions.find(t => t.key === value);
- if (tableInfo) {
- link.display_fields = {
- title: tableInfo.title_field,
- subtitle: tableInfo.subtitle_field
- };
+ // 직접 입력 필드 동기화
+ const customInput = document.getElementById('link-source-table-custom');
+ if (customInput) {
+ const isKnown = currentValue && sourceTableOptions.find(t => t.key === currentValue);
+ if (isKnown) {
+ customInput.value = '';
+ customInput.disabled = true;
+ } else {
+ customInput.disabled = false;
+ customInput.value = currentValue || '';
+ }
}
- renderTemplateLinks();
+ // link_type 동기화
+ const typeSelect = document.getElementById('link-type');
+ if (typeSelect && link) {
+ typeSelect.value = link.link_type || 'multiple';
+ }
+
+ // 소스 테이블이 설정되어 있으면 검색 패널 표시 + 태그 렌더
+ if (currentValue) {
+ document.getElementById('link-search-panel')?.classList.remove('hidden');
+ renderLinkValueTags();
+ }
+ }
+
+ function onLinkSourceTableChange(value) {
+ const link = ensureMainLink();
+ const panel = document.getElementById('link-search-panel');
+ const customInput = document.getElementById('link-source-table-custom');
+
+ if (!value) {
+ link.source_table = '';
+ link.values = [];
+ panel?.classList.add('hidden');
+ if (customInput) { customInput.disabled = false; customInput.value = ''; }
+ renderLinkValueTags();
+ return;
+ }
+
+ link.source_table = value;
+ link.values = [];
+ link.link_key = value;
+ if (customInput) { customInput.disabled = true; customInput.value = ''; }
+
+ // 메타 정보 자동 설정
+ const tableInfo = sourceTableOptions.find(t => t.key === value);
+ if (tableInfo) {
+ link.label = tableInfo.label || value;
+ link.display_fields = { title: tableInfo.title_field, subtitle: tableInfo.subtitle_field };
+ }
+
+ panel?.classList.remove('hidden');
+ checkedItems.clear();
+ renderLinkValueTags();
+
+ // 검색 초기화
+ const searchInput = document.getElementById('link-search-input');
+ if (searchInput) searchInput.value = '';
+ document.getElementById('link-search-results')?.classList.add('hidden');
+ }
+
+ function onLinkSourceTableCustom(value) {
+ const link = ensureMainLink();
+ const panel = document.getElementById('link-search-panel');
+ const select = document.getElementById('link-source-table');
+
+ if (!value) {
+ link.source_table = '';
+ link.values = [];
+ panel?.classList.add('hidden');
+ if (select) select.value = '';
+ renderLinkValueTags();
+ return;
+ }
+
+ link.source_table = value;
+ link.values = [];
+ link.link_key = value;
+ link.label = value;
+ if (select) select.value = '';
+
+ panel?.classList.remove('hidden');
+ checkedItems.clear();
+ renderLinkValueTags();
+
+ const searchInput = document.getElementById('link-search-input');
+ if (searchInput) searchInput.value = '';
+ document.getElementById('link-search-results')?.classList.add('hidden');
+ }
+
+ function onLinkTypeChange(value) {
+ const link = ensureMainLink();
+ link.link_type = value;
+
+ // 단일로 변경 시 첫 번째만 유지
+ if (value === 'single' && link.values.length > 1) {
+ link.values = [link.values[0]];
+ renderLinkValueTags();
+ }
}
async function searchLinkValues(linkId, query) {
- const link = templateState.template_links.find(l => l.id == linkId);
+ const link = getMainLink();
if (!link || !link.source_table) return;
- clearTimeout(linkSearchTimers[linkId]);
- const resultsEl = document.getElementById(`link-results-${linkId}`);
+ clearTimeout(linkSearchTimer);
+ const resultsEl = document.getElementById('link-search-results');
if (!resultsEl) return;
if (query.length < 1) {
resultsEl.classList.add('hidden');
+ document.getElementById('btn-add-checked')?.classList.add('hidden');
+ checkedItems.clear();
return;
}
- linkSearchTimers[linkId] = setTimeout(async () => {
+ linkSearchTimer = setTimeout(async () => {
try {
let url = `/api/admin/source-tables/${link.source_table}/search?q=${encodeURIComponent(query)}`;
- // search_params 추가
if (link.search_params) {
Object.entries(link.search_params).forEach(([k, v]) => {
url += `&${k}=${encodeURIComponent(v)}`;
@@ -1694,19 +1725,31 @@ function onSourceTableChange(linkId, value) {
const subtitleField = json.meta.subtitle_field;
const existingIds = (link.values || []).map(v => v.linkable_id || v.id);
- resultsEl.innerHTML = json.data
- .filter(item => !existingIds.includes(item.id))
- .map(item => `
-
-
${escapeHtml(item[titleField] || '')}
- ${item[subtitleField] ? `
${escapeHtml(item[subtitleField] || '')}
` : ''}
-
- `).join('');
+ checkedItems.clear();
+ resultsEl.innerHTML = json.data.map(item => {
+ const isLinked = existingIds.includes(item.id);
+ return `
+
+ `;
+ }).join('');
resultsEl.classList.remove('hidden');
+ updateAddCheckedButton();
} else {
resultsEl.innerHTML = '검색 결과 없음
';
resultsEl.classList.remove('hidden');
+ document.getElementById('btn-add-checked')?.classList.add('hidden');
}
} catch (e) {
console.error('연결 검색 실패:', e);
@@ -1714,58 +1757,85 @@ function onSourceTableChange(linkId, value) {
}, 300);
}
- function selectLinkValue(linkId, itemId, title, subtitle) {
- const link = templateState.template_links.find(l => l.id == linkId);
- if (!link) return;
+ function onSearchCheckChange(checkbox) {
+ const id = parseInt(checkbox.value);
+ if (checkbox.checked) {
+ checkedItems.set(id, {
+ id: id,
+ title: checkbox.dataset.title,
+ subtitle: checkbox.dataset.subtitle
+ });
+ } else {
+ checkedItems.delete(id);
+ }
+ updateAddCheckedButton();
+ }
+ function updateAddCheckedButton() {
+ const btn = document.getElementById('btn-add-checked');
+ if (!btn) return;
+ if (checkedItems.size > 0) {
+ btn.classList.remove('hidden');
+ btn.textContent = `선택 추가 (${checkedItems.size})`;
+ } else {
+ btn.classList.add('hidden');
+ }
+ }
+
+ function addCheckedLinkValues() {
+ const link = getMainLink();
+ if (!link) return;
if (!link.values) link.values = [];
- // 단일 연결이면 기존 값 교체
+ // 단일이면 마지막 체크만
if (link.link_type === 'single') {
- link.values = [{ linkable_id: itemId, display_text: title + (subtitle ? ` (${subtitle})` : '') }];
- } else {
- // 중복 체크
- if (!link.values.find(v => (v.linkable_id || v.id) === itemId)) {
- link.values.push({ linkable_id: itemId, display_text: title + (subtitle ? ` (${subtitle})` : '') });
+ const last = Array.from(checkedItems.values()).pop();
+ if (last) {
+ link.values = [{ linkable_id: last.id, display_text: last.title + (last.subtitle ? ` (${last.subtitle})` : '') }];
}
+ } else {
+ checkedItems.forEach(item => {
+ if (!link.values.find(v => (v.linkable_id || v.id) === item.id)) {
+ link.values.push({ linkable_id: item.id, display_text: item.title + (item.subtitle ? ` (${item.subtitle})` : '') });
+ }
+ });
}
- // 검색 결과 닫기
- const resultsEl = document.getElementById(`link-results-${linkId}`);
- if (resultsEl) resultsEl.classList.add('hidden');
- const searchInput = document.getElementById(`link-search-${linkId}`);
- if (searchInput) searchInput.value = '';
-
- // 값 태그 다시 렌더링
- renderLinkValueTags(linkId);
+ checkedItems.clear();
+ // 검색 결과 닫고 초기화
+ document.getElementById('link-search-results')?.classList.add('hidden');
+ document.getElementById('btn-add-checked')?.classList.add('hidden');
+ document.getElementById('link-search-input').value = '';
+ renderLinkValueTags();
}
- function removeLinkValue(linkId, itemId) {
- const link = templateState.template_links.find(l => l.id == linkId);
+ function removeLinkValue(itemId) {
+ const link = getMainLink();
if (!link) return;
-
link.values = (link.values || []).filter(v => (v.linkable_id || v.id) !== itemId);
- renderLinkValueTags(linkId);
+ renderLinkValueTags();
}
- function renderLinkValueTags(linkId) {
- const link = templateState.template_links.find(l => l.id == linkId);
- const container = document.getElementById(`link-values-${linkId}`);
- if (!container || !link) return;
+ function renderLinkValueTags() {
+ const container = document.getElementById('link-value-tags');
+ if (!container) return;
+ const link = getMainLink();
+ if (!link || !link.values || link.values.length === 0) {
+ container.innerHTML = '';
+ return;
+ }
- container.innerHTML = (link.values || []).map(v => `
+ container.innerHTML = link.values.map(v => `
${escapeHtml(v.display_text || 'ID: ' + (v.linkable_id || v.id))}
-
+
`).join('');
}
// 페이지 로드 시 소스 테이블 옵션 로드
loadSourceTableOptions().then(() => {
- if (templateState.template_links.length > 0) {
- renderTemplateLinks();
- }
+ populateLinkSourceTable();
});
// ===== 동적 필드 렌더링 (검사 기준서 셀) =====
@@ -1841,31 +1911,32 @@ class="w-full px-2 py-1 border border-gray-200 rounded text-xs">`;
case 'json_tolerance':
return renderToleranceInput(sid, iid, val || item.tolerance);
- case 'json_criteria': {
- const c = val || item.standard_criteria;
- // 기준값 + min/max 범위 복합 UI
- const stdVal = getItemFieldValue(item, 'standard');
- return `
-
-
-
-
~
-
-
+ case 'text_with_criteria': {
+ // 상단: 검사기준 텍스트, 하단: 기준범위 min/max
+ const c = getItemFieldValue(item, 'standard_criteria') || item.standard_criteria;
+ return `
`;
}
diff --git a/resources/views/documents/edit.blade.php b/resources/views/documents/edit.blade.php
index 970f6131..44a21383 100644
--- a/resources/views/documents/edit.blade.php
+++ b/resources/views/documents/edit.blade.php
@@ -140,6 +140,22 @@ class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:rin
 }})
@endif
+ {{-- 연결 품목 규격 정보 --}}
+ @if(!empty($linkedItemSpecs))
+
+ 연결 품목 규격:
+ @foreach($linkedItemSpecs as $spec)
+
+ {{ $spec['name'] }}
+ @if($spec['thickness'])t={{ $spec['thickness'] }}@endif
+ @if($spec['width'])w={{ $spec['width'] }}@endif
+ @if($spec['length'])l={{ $spec['length'] }}@endif
+
+ @endforeach
+ ← 해당 범위 행이 노란색으로 표시됩니다
+
+ @endif
+
{{-- 검사 데이터 테이블 --}}
@if($section->items->count() > 0 && $template->columns->count() > 0)
@php
@@ -230,6 +246,39 @@ class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:rin
$idx++;
}
+ // 연결 품목 규격 정보 (자동 하이라이트용)
+ $itemSpecs = $linkedItemSpecs ?? [];
+ $matchRow = function($item) use ($itemSpecs) {
+ $c = $item->getFieldValue('standard_criteria');
+ if (!$c || !is_array($c) || (!isset($c['min']) && !isset($c['max']))) {
+ return false;
+ }
+
+ // 검사항목명에서 비교 대상 결정 (두께→thickness, 너비→width, 길이→length)
+ $itemName = mb_strtolower(trim($item->getFieldValue('item') ?? ''));
+ $specKey = 'thickness'; // 기본값
+ if (str_contains($itemName, '너비') || str_contains($itemName, 'width')) {
+ $specKey = 'width';
+ } elseif (str_contains($itemName, '길이') || str_contains($itemName, 'length')) {
+ $specKey = 'length';
+ }
+
+ foreach ($itemSpecs as $spec) {
+ $val = $spec[$specKey] ?? null;
+ if ($val === null) continue;
+
+ $min = isset($c['min']) ? (float)$c['min'] : null;
+ $max = isset($c['max']) ? (float)$c['max'] : null;
+ $minOp = $c['min_op'] ?? 'gte';
+ $maxOp = $c['max_op'] ?? 'lte';
+
+ $minOk = $min === null || ($minOp === 'gt' ? $val > $min : $val >= $min);
+ $maxOk = $max === null || ($maxOp === 'lt' ? $val < $max : $val <= $max);
+ if ($minOk && $maxOk) return true;
+ }
+ return false;
+ };
+
// 측정치 컬럼 정보
$hasComplex = $template->columns->contains(fn($c) => $c->column_type === 'complex' && $c->sub_labels);
$maxFreqN = $allItems->max(fn($i) => $i->getFieldValue('frequency_n')) ?: 0;
@@ -300,8 +349,8 @@ class="px-2 py-2 text-center text-xs font-medium text-gray-600 uppercase border
@php $rowNum++; @endphp
@if($row['type'] === 'single')
{{-- 단일 항목 --}}
- @php $item = $row['item']; $rowIndex = $globalRowIndex; $globalRowIndex++; @endphp
-
+ @php $item = $row['item']; $rowIndex = $globalRowIndex; $globalRowIndex++; $isMatch = $matchRow($item); @endphp
+
@foreach($template->columns as $col)
@if($col->column_type === 'complex' && $col->sub_labels)
{{-- 측정치: measurement_type에 따라 분기 (getFieldValue 사용) --}}
@@ -472,8 +521,8 @@ class="w-full text-center text-sm border-0 focus:ring-1 focus:ring-blue-400 roun
{{-- 그룹 항목 --}}
@php $groupItems = $row['items']; $groupCount = count($groupItems); @endphp
@foreach($groupItems as $itemIdx => $item)
- @php $rowIndex = $globalRowIndex; $globalRowIndex++; @endphp
-
+ @php $rowIndex = $globalRowIndex; $globalRowIndex++; $isMatch = $matchRow($item); @endphp
+
@foreach($template->columns as $col)
@if($col->column_type === 'complex' && $col->sub_labels)
{{-- 측정치: 각 행 개별 렌더링 (getFieldValue 사용) --}}
diff --git a/resources/views/documents/index.blade.php b/resources/views/documents/index.blade.php
index fe812456..868447cb 100644
--- a/resources/views/documents/index.blade.php
+++ b/resources/views/documents/index.blade.php
@@ -43,6 +43,9 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc
@foreach($statuses as $value => $label)
@endforeach
+ @if(auth()->user()?->is_super_admin)
+
+ @endif
@@ -108,6 +111,8 @@ class="w-full sm:w-36 px-3 py-2 border border-gray-300 rounded-lg focus:outline-
@push('scripts')
-@endpush
\ No newline at end of file
+@endpush