feat:검사 기준서 동적화 + 소스 테이블 통합 검색

- 동적 필드/연결 모델 추가 (SectionField, Link, LinkValue, Preset)
- 통합 검색 API (SourceTableSearchController) - items/processes/lots/users
- 템플릿 편집 UI: 소스 테이블 드롭다운 + datalist 검색/선택
- 문서 작성/인쇄/상세 뷰: getFieldValue() 기반 동적 렌더링
- DocumentTemplateApiController: source_table 기반 저장/복제
- DocumentController: sectionFields/links eager loading 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-04 08:38:00 +09:00
parent fff3682ff7
commit cb097ad523
16 changed files with 1719 additions and 462 deletions

View File

@@ -3,9 +3,11 @@
namespace App\Http\Controllers;
use App\Models\DocumentTemplate;
use App\Models\DocumentTemplateFieldPreset;
use App\Models\Tenants\Tenant;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;
class DocumentTemplateController extends Controller
@@ -31,6 +33,7 @@ public function create(): View
'isCreate' => true,
'categories' => $this->getCategories(),
'tenant' => $this->getCurrentTenant(),
'presets' => DocumentTemplateFieldPreset::orderBy('sort_order')->get(),
]);
}
@@ -44,6 +47,8 @@ public function edit(int $id): View
'basicFields',
'sections.items',
'columns',
'sectionFields',
'links.linkValues',
])->findOrFail($id);
// JavaScript용 데이터 변환
@@ -55,6 +60,7 @@ public function edit(int $id): View
'isCreate' => false,
'categories' => $this->getCategories(),
'tenant' => $this->getCurrentTenant(),
'presets' => DocumentTemplateFieldPreset::orderBy('sort_order')->get(),
]);
}
@@ -162,6 +168,79 @@ private function prepareTemplateData(DocumentTemplate $template): array
'sub_labels' => $c->sub_labels,
];
})->toArray(),
'section_fields' => $template->sectionFields->map(function ($f) {
return [
'id' => $f->id,
'field_key' => $f->field_key,
'label' => $f->label,
'field_type' => $f->field_type,
'options' => $f->options,
'width' => $f->width,
'is_required' => $f->is_required,
];
})->toArray(),
'template_links' => $template->links->map(function ($l) {
$values = $l->linkValues->map(function ($v) use ($l) {
$displayText = $this->resolveDisplayText($l->source_table, $v->linkable_id, $l->display_fields);
return [
'id' => $v->id,
'linkable_id' => $v->linkable_id,
'display_text' => $displayText,
];
})->toArray();
return [
'id' => $l->id,
'link_key' => $l->link_key,
'label' => $l->label,
'link_type' => $l->link_type,
'source_table' => $l->source_table,
'search_params' => $l->search_params,
'display_fields' => $l->display_fields,
'is_required' => $l->is_required,
'values' => $values,
];
})->toArray(),
];
}
}
/**
* 소스 테이블에서 레코드의 표시 텍스트 조회
*/
private function resolveDisplayText(?string $sourceTable, int $linkableId, ?array $displayFields): string
{
if (! $sourceTable || ! $linkableId) {
return "ID: {$linkableId}";
}
$titleField = $displayFields['title'] ?? 'name';
$subtitleField = $displayFields['subtitle'] ?? null;
// 모델 매핑
$modelMap = [
'items' => \App\Models\Items\Item::class,
'processes' => \App\Models\Process::class,
'users' => \App\Models\User::class,
];
try {
if (isset($modelMap[$sourceTable])) {
$record = $modelMap[$sourceTable]::find($linkableId);
} else {
$record = DB::table($sourceTable)->find($linkableId);
}
if (! $record) {
return "ID: {$linkableId}";
}
$title = is_object($record) ? ($record->$titleField ?? '') : ($record->$titleField ?? '');
$subtitle = $subtitleField ? (is_object($record) ? ($record->$subtitleField ?? '') : ($record->$subtitleField ?? '')) : '';
return $title . ($subtitle ? " ({$subtitle})" : '');
} catch (\Exception $e) {
return "ID: {$linkableId}";
}
}
}