2026-01-27 11:31:02 +09:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers\Api\Admin;
|
|
|
|
|
|
|
|
|
|
use App\Http\Controllers\Controller;
|
|
|
|
|
use App\Models\DocumentTemplate;
|
|
|
|
|
use App\Models\DocumentTemplateApprovalLine;
|
|
|
|
|
use App\Models\DocumentTemplateBasicField;
|
|
|
|
|
use App\Models\DocumentTemplateColumn;
|
2026-02-04 08:38:00 +09:00
|
|
|
use App\Models\DocumentTemplateLink;
|
|
|
|
|
use App\Models\DocumentTemplateLinkValue;
|
2026-01-27 11:31:02 +09:00
|
|
|
use App\Models\DocumentTemplateSection;
|
2026-02-04 08:38:00 +09:00
|
|
|
use App\Models\DocumentTemplateSectionField;
|
2026-01-27 11:31:02 +09:00
|
|
|
use App\Models\DocumentTemplateSectionItem;
|
|
|
|
|
use Illuminate\Http\JsonResponse;
|
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
|
use Illuminate\View\View;
|
|
|
|
|
|
|
|
|
|
class DocumentTemplateApiController extends Controller
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* 목록 조회 (HTMX 테이블)
|
|
|
|
|
*/
|
|
|
|
|
public function index(Request $request): View
|
|
|
|
|
{
|
|
|
|
|
$query = DocumentTemplate::query()
|
|
|
|
|
->withCount(['sections', 'columns']);
|
|
|
|
|
|
2026-02-02 18:04:37 +09:00
|
|
|
// 선택된 테넌트 필터
|
|
|
|
|
$tenantId = session('selected_tenant_id');
|
|
|
|
|
if ($tenantId) {
|
|
|
|
|
$query->where('tenant_id', $tenantId);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-03 10:33:48 +09:00
|
|
|
// 슈퍼관리자 휴지통 조회
|
|
|
|
|
$showTrashed = $request->filled('trashed') && auth()->user()?->is_super_admin;
|
|
|
|
|
if ($showTrashed) {
|
|
|
|
|
$query->onlyTrashed();
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-27 11:31:02 +09:00
|
|
|
// 검색
|
|
|
|
|
if ($search = $request->input('search')) {
|
|
|
|
|
$query->where(function ($q) use ($search) {
|
|
|
|
|
$q->where('name', 'like', "%{$search}%")
|
|
|
|
|
->orWhere('title', 'like', "%{$search}%")
|
|
|
|
|
->orWhere('category', 'like', "%{$search}%");
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 카테고리 필터
|
|
|
|
|
if ($category = $request->input('category')) {
|
|
|
|
|
$query->where('category', $category);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 활성 상태 필터
|
2026-02-03 10:33:48 +09:00
|
|
|
if ($request->filled('is_active') && !$showTrashed) {
|
2026-01-27 11:31:02 +09:00
|
|
|
$query->where('is_active', $request->boolean('is_active'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$templates = $query->orderBy('updated_at', 'desc')
|
|
|
|
|
->paginate($request->input('per_page', 10));
|
|
|
|
|
|
2026-02-03 10:33:48 +09:00
|
|
|
return view('document-templates.partials.table', compact('templates', 'showTrashed'));
|
2026-01-27 11:31:02 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 단일 조회
|
|
|
|
|
*/
|
|
|
|
|
public function show(int $id): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
$template = DocumentTemplate::with([
|
|
|
|
|
'approvalLines',
|
|
|
|
|
'basicFields',
|
|
|
|
|
'sections.items',
|
|
|
|
|
'columns',
|
2026-02-04 08:38:00 +09:00
|
|
|
'sectionFields',
|
|
|
|
|
'links.linkValues',
|
2026-01-27 11:31:02 +09:00
|
|
|
])->findOrFail($id);
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => true,
|
|
|
|
|
'data' => $template,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 생성
|
|
|
|
|
*/
|
|
|
|
|
public function store(Request $request): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
$validated = $request->validate([
|
|
|
|
|
'name' => 'required|string|max:100',
|
|
|
|
|
'category' => 'nullable|string|max:50',
|
|
|
|
|
'title' => 'nullable|string|max:200',
|
|
|
|
|
'company_name' => 'nullable|string|max:100',
|
|
|
|
|
'company_address' => 'nullable|string|max:255',
|
|
|
|
|
'company_contact' => 'nullable|string|max:100',
|
|
|
|
|
'footer_remark_label' => 'nullable|string|max:50',
|
|
|
|
|
'footer_judgement_label' => 'nullable|string|max:50',
|
|
|
|
|
'footer_judgement_options' => 'nullable|array',
|
|
|
|
|
'is_active' => 'boolean',
|
2026-02-03 10:33:48 +09:00
|
|
|
'linked_item_ids' => 'nullable|array',
|
|
|
|
|
'linked_item_ids.*' => 'integer',
|
|
|
|
|
'linked_process_id' => 'nullable|integer',
|
2026-01-27 11:31:02 +09:00
|
|
|
// 관계 데이터
|
|
|
|
|
'approval_lines' => 'nullable|array',
|
|
|
|
|
'basic_fields' => 'nullable|array',
|
|
|
|
|
'sections' => 'nullable|array',
|
|
|
|
|
'columns' => 'nullable|array',
|
2026-02-04 08:38:00 +09:00
|
|
|
'section_fields' => 'nullable|array',
|
|
|
|
|
'template_links' => 'nullable|array',
|
2026-01-27 11:31:02 +09:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
DB::beginTransaction();
|
|
|
|
|
|
|
|
|
|
$template = DocumentTemplate::create([
|
|
|
|
|
'tenant_id' => session('selected_tenant_id'),
|
|
|
|
|
'name' => $validated['name'],
|
|
|
|
|
'category' => $validated['category'] ?? null,
|
|
|
|
|
'title' => $validated['title'] ?? null,
|
|
|
|
|
'company_name' => $validated['company_name'] ?? '경동기업',
|
|
|
|
|
'company_address' => $validated['company_address'] ?? null,
|
|
|
|
|
'company_contact' => $validated['company_contact'] ?? null,
|
|
|
|
|
'footer_remark_label' => $validated['footer_remark_label'] ?? '부적합 내용',
|
|
|
|
|
'footer_judgement_label' => $validated['footer_judgement_label'] ?? '종합판정',
|
|
|
|
|
'footer_judgement_options' => $validated['footer_judgement_options'] ?? ['적합', '부적합'],
|
|
|
|
|
'is_active' => $validated['is_active'] ?? true,
|
2026-02-03 10:33:48 +09:00
|
|
|
'linked_item_ids' => $validated['linked_item_ids'] ?? null,
|
|
|
|
|
'linked_process_id' => $validated['linked_process_id'] ?? null,
|
2026-01-27 11:31:02 +09:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// 관계 데이터 저장
|
|
|
|
|
$this->saveRelations($template, $validated);
|
|
|
|
|
|
|
|
|
|
DB::commit();
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => true,
|
|
|
|
|
'message' => '문서양식이 생성되었습니다.',
|
2026-02-04 08:38:00 +09:00
|
|
|
'data' => $template->load(['approvalLines', 'basicFields', 'sections.items', 'columns', 'sectionFields', 'links.linkValues']),
|
2026-01-27 11:31:02 +09:00
|
|
|
]);
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
DB::rollBack();
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => false,
|
|
|
|
|
'message' => '생성 중 오류가 발생했습니다: '.$e->getMessage(),
|
|
|
|
|
], 500);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 수정
|
|
|
|
|
*/
|
|
|
|
|
public function update(Request $request, int $id): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
$template = DocumentTemplate::findOrFail($id);
|
|
|
|
|
|
|
|
|
|
$validated = $request->validate([
|
|
|
|
|
'name' => 'required|string|max:100',
|
|
|
|
|
'category' => 'nullable|string|max:50',
|
|
|
|
|
'title' => 'nullable|string|max:200',
|
|
|
|
|
'company_name' => 'nullable|string|max:100',
|
|
|
|
|
'company_address' => 'nullable|string|max:255',
|
|
|
|
|
'company_contact' => 'nullable|string|max:100',
|
|
|
|
|
'footer_remark_label' => 'nullable|string|max:50',
|
|
|
|
|
'footer_judgement_label' => 'nullable|string|max:50',
|
|
|
|
|
'footer_judgement_options' => 'nullable|array',
|
|
|
|
|
'is_active' => 'boolean',
|
2026-02-03 10:33:48 +09:00
|
|
|
'linked_item_ids' => 'nullable|array',
|
|
|
|
|
'linked_item_ids.*' => 'integer',
|
|
|
|
|
'linked_process_id' => 'nullable|integer',
|
2026-01-27 11:31:02 +09:00
|
|
|
// 관계 데이터
|
|
|
|
|
'approval_lines' => 'nullable|array',
|
|
|
|
|
'basic_fields' => 'nullable|array',
|
|
|
|
|
'sections' => 'nullable|array',
|
|
|
|
|
'columns' => 'nullable|array',
|
2026-02-04 08:38:00 +09:00
|
|
|
'section_fields' => 'nullable|array',
|
|
|
|
|
'template_links' => 'nullable|array',
|
2026-01-27 11:31:02 +09:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
DB::beginTransaction();
|
|
|
|
|
|
|
|
|
|
$template->update([
|
|
|
|
|
'name' => $validated['name'],
|
|
|
|
|
'category' => $validated['category'] ?? null,
|
|
|
|
|
'title' => $validated['title'] ?? null,
|
|
|
|
|
'company_name' => $validated['company_name'] ?? null,
|
|
|
|
|
'company_address' => $validated['company_address'] ?? null,
|
|
|
|
|
'company_contact' => $validated['company_contact'] ?? null,
|
|
|
|
|
'footer_remark_label' => $validated['footer_remark_label'] ?? '부적합 내용',
|
|
|
|
|
'footer_judgement_label' => $validated['footer_judgement_label'] ?? '종합판정',
|
|
|
|
|
'footer_judgement_options' => $validated['footer_judgement_options'] ?? ['적합', '부적합'],
|
|
|
|
|
'is_active' => $validated['is_active'] ?? true,
|
2026-02-03 10:33:48 +09:00
|
|
|
'linked_item_ids' => $validated['linked_item_ids'] ?? null,
|
|
|
|
|
'linked_process_id' => $validated['linked_process_id'] ?? null,
|
2026-01-27 11:31:02 +09:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// 관계 데이터 저장 (기존 데이터 삭제 후 재생성)
|
|
|
|
|
$this->saveRelations($template, $validated, true);
|
|
|
|
|
|
|
|
|
|
DB::commit();
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => true,
|
|
|
|
|
'message' => '문서양식이 수정되었습니다.',
|
2026-02-04 08:38:00 +09:00
|
|
|
'data' => $template->fresh(['approvalLines', 'basicFields', 'sections.items', 'columns', 'sectionFields', 'links.linkValues']),
|
2026-01-27 11:31:02 +09:00
|
|
|
]);
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
DB::rollBack();
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => false,
|
|
|
|
|
'message' => '수정 중 오류가 발생했습니다: '.$e->getMessage(),
|
|
|
|
|
], 500);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-02-03 10:33:48 +09:00
|
|
|
* 삭제 (소프트 삭제)
|
2026-01-27 11:31:02 +09:00
|
|
|
*/
|
|
|
|
|
public function destroy(int $id): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
$template = DocumentTemplate::findOrFail($id);
|
2026-02-03 10:33:48 +09:00
|
|
|
$template->update(['deleted_by' => auth()->id()]);
|
2026-01-27 11:31:02 +09:00
|
|
|
$template->delete();
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => true,
|
|
|
|
|
'message' => '문서양식이 삭제되었습니다.',
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-03 10:33:48 +09:00
|
|
|
/**
|
|
|
|
|
* 영구삭제 (슈퍼관리자 전용)
|
|
|
|
|
*/
|
|
|
|
|
public function forceDestroy(int $id): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
if (!auth()->user()?->is_super_admin) {
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => false,
|
|
|
|
|
'message' => '슈퍼관리자만 영구 삭제할 수 있습니다.',
|
|
|
|
|
], 403);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$tenantId = session('selected_tenant_id');
|
|
|
|
|
$template = DocumentTemplate::withTrashed()
|
|
|
|
|
->where('tenant_id', $tenantId)
|
|
|
|
|
->findOrFail($id);
|
|
|
|
|
|
|
|
|
|
// 이 양식을 참조하는 문서가 있는지 확인 (소프트삭제 포함)
|
|
|
|
|
$documentCount = \App\Models\Documents\Document::withTrashed()
|
|
|
|
|
->where('template_id', $template->id)
|
|
|
|
|
->count();
|
|
|
|
|
|
|
|
|
|
if ($documentCount > 0) {
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => false,
|
|
|
|
|
'message' => "이 양식을 사용한 문서가 {$documentCount}건 있어 영구 삭제할 수 없습니다. 문서를 먼저 삭제해주세요.",
|
|
|
|
|
], 422);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 관련 데이터도 영구삭제
|
|
|
|
|
$template->approvalLines()->delete();
|
|
|
|
|
$template->basicFields()->delete();
|
|
|
|
|
$template->sections()->each(function ($section) {
|
|
|
|
|
$section->items()->delete();
|
|
|
|
|
$section->delete();
|
|
|
|
|
});
|
|
|
|
|
$template->columns()->delete();
|
2026-02-04 08:38:00 +09:00
|
|
|
$template->sectionFields()->delete();
|
|
|
|
|
$template->links()->delete(); // cascade로 linkValues도 삭제
|
2026-02-03 10:33:48 +09:00
|
|
|
$template->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');
|
|
|
|
|
$template = DocumentTemplate::onlyTrashed()
|
|
|
|
|
->where('tenant_id', $tenantId)
|
|
|
|
|
->findOrFail($id);
|
|
|
|
|
|
|
|
|
|
$template->update(['deleted_by' => null]);
|
|
|
|
|
$template->restore();
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => true,
|
|
|
|
|
'message' => '문서양식이 복원되었습니다.',
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-27 11:31:02 +09:00
|
|
|
/**
|
|
|
|
|
* 활성 상태 토글
|
|
|
|
|
*/
|
|
|
|
|
public function toggleActive(int $id): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
$template = DocumentTemplate::findOrFail($id);
|
|
|
|
|
$template->update(['is_active' => ! $template->is_active]);
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => true,
|
|
|
|
|
'message' => $template->is_active ? '활성화되었습니다.' : '비활성화되었습니다.',
|
|
|
|
|
'is_active' => $template->is_active,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-31 04:32:35 +09:00
|
|
|
/**
|
|
|
|
|
* 양식 복제
|
|
|
|
|
*/
|
|
|
|
|
public function duplicate(Request $request, int $id): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
$source = DocumentTemplate::with([
|
|
|
|
|
'approvalLines',
|
|
|
|
|
'basicFields',
|
|
|
|
|
'sections.items',
|
|
|
|
|
'columns',
|
2026-02-04 08:38:00 +09:00
|
|
|
'sectionFields',
|
|
|
|
|
'links.linkValues',
|
2026-01-31 04:32:35 +09:00
|
|
|
])->findOrFail($id);
|
|
|
|
|
|
|
|
|
|
$newName = $request->input('name', $source->name.' (복사)');
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
DB::beginTransaction();
|
|
|
|
|
|
|
|
|
|
$newTemplate = DocumentTemplate::create([
|
|
|
|
|
'tenant_id' => $source->tenant_id,
|
|
|
|
|
'name' => $newName,
|
|
|
|
|
'category' => $source->category,
|
|
|
|
|
'title' => $source->title,
|
|
|
|
|
'company_name' => $source->company_name,
|
|
|
|
|
'company_address' => $source->company_address,
|
|
|
|
|
'company_contact' => $source->company_contact,
|
|
|
|
|
'footer_remark_label' => $source->footer_remark_label,
|
|
|
|
|
'footer_judgement_label' => $source->footer_judgement_label,
|
|
|
|
|
'footer_judgement_options' => $source->footer_judgement_options,
|
|
|
|
|
'is_active' => false,
|
2026-02-03 10:33:48 +09:00
|
|
|
'linked_item_ids' => $source->linked_item_ids,
|
|
|
|
|
'linked_process_id' => $source->linked_process_id,
|
2026-01-31 04:32:35 +09:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
foreach ($source->approvalLines as $line) {
|
|
|
|
|
DocumentTemplateApprovalLine::create([
|
|
|
|
|
'template_id' => $newTemplate->id,
|
|
|
|
|
'name' => $line->name,
|
|
|
|
|
'dept' => $line->dept,
|
|
|
|
|
'role' => $line->role,
|
2026-02-03 10:33:48 +09:00
|
|
|
'user_id' => $line->user_id,
|
2026-01-31 04:32:35 +09:00
|
|
|
'sort_order' => $line->sort_order,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach ($source->basicFields as $field) {
|
|
|
|
|
DocumentTemplateBasicField::create([
|
|
|
|
|
'template_id' => $newTemplate->id,
|
|
|
|
|
'label' => $field->label,
|
|
|
|
|
'field_type' => $field->field_type,
|
|
|
|
|
'default_value' => $field->default_value,
|
|
|
|
|
'sort_order' => $field->sort_order,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach ($source->sections as $section) {
|
|
|
|
|
$newSection = DocumentTemplateSection::create([
|
|
|
|
|
'template_id' => $newTemplate->id,
|
|
|
|
|
'title' => $section->title,
|
|
|
|
|
'image_path' => $section->image_path,
|
|
|
|
|
'sort_order' => $section->sort_order,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
foreach ($section->items as $item) {
|
|
|
|
|
DocumentTemplateSectionItem::create([
|
|
|
|
|
'section_id' => $newSection->id,
|
|
|
|
|
'category' => $item->category,
|
|
|
|
|
'item' => $item->item,
|
|
|
|
|
'standard' => $item->standard,
|
2026-02-02 16:29:45 +09:00
|
|
|
'tolerance' => $item->tolerance,
|
2026-02-02 20:37:06 +09:00
|
|
|
'standard_criteria' => $item->standard_criteria,
|
2026-01-31 04:32:35 +09:00
|
|
|
'method' => $item->method,
|
2026-02-02 16:29:45 +09:00
|
|
|
'measurement_type' => $item->measurement_type,
|
2026-02-02 20:37:06 +09:00
|
|
|
'frequency_n' => $item->frequency_n,
|
|
|
|
|
'frequency_c' => $item->frequency_c,
|
2026-01-31 04:32:35 +09:00
|
|
|
'frequency' => $item->frequency,
|
|
|
|
|
'regulation' => $item->regulation,
|
|
|
|
|
'sort_order' => $item->sort_order,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach ($source->columns as $col) {
|
|
|
|
|
DocumentTemplateColumn::create([
|
|
|
|
|
'template_id' => $newTemplate->id,
|
|
|
|
|
'label' => $col->label,
|
|
|
|
|
'width' => $col->width,
|
|
|
|
|
'column_type' => $col->column_type,
|
|
|
|
|
'group_name' => $col->group_name,
|
|
|
|
|
'sub_labels' => $col->sub_labels,
|
|
|
|
|
'sort_order' => $col->sort_order,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-04 08:38:00 +09:00
|
|
|
// 검사 기준서 동적 필드 복제
|
|
|
|
|
foreach ($source->sectionFields as $field) {
|
|
|
|
|
DocumentTemplateSectionField::create([
|
|
|
|
|
'template_id' => $newTemplate->id,
|
|
|
|
|
'field_key' => $field->field_key,
|
|
|
|
|
'label' => $field->label,
|
|
|
|
|
'field_type' => $field->field_type,
|
|
|
|
|
'options' => $field->options,
|
|
|
|
|
'width' => $field->width,
|
|
|
|
|
'is_required' => $field->is_required,
|
|
|
|
|
'sort_order' => $field->sort_order,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 외부 키 매핑 복제
|
|
|
|
|
foreach ($source->links as $link) {
|
|
|
|
|
$newLink = DocumentTemplateLink::create([
|
|
|
|
|
'template_id' => $newTemplate->id,
|
|
|
|
|
'link_key' => $link->link_key,
|
|
|
|
|
'label' => $link->label,
|
|
|
|
|
'link_type' => $link->link_type,
|
|
|
|
|
'source_table' => $link->source_table,
|
|
|
|
|
'search_params' => $link->search_params,
|
|
|
|
|
'display_fields' => $link->display_fields,
|
|
|
|
|
'is_required' => $link->is_required,
|
|
|
|
|
'sort_order' => $link->sort_order,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
foreach ($link->linkValues as $value) {
|
|
|
|
|
DocumentTemplateLinkValue::create([
|
|
|
|
|
'template_id' => $newTemplate->id,
|
|
|
|
|
'link_id' => $newLink->id,
|
|
|
|
|
'linkable_id' => $value->linkable_id,
|
|
|
|
|
'sort_order' => $value->sort_order,
|
|
|
|
|
'created_at' => now(),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-31 04:32:35 +09:00
|
|
|
DB::commit();
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => true,
|
|
|
|
|
'message' => "'{$newName}' 양식이 복제되었습니다.",
|
|
|
|
|
'data' => $newTemplate->load(['approvalLines', 'basicFields', 'sections.items', 'columns']),
|
|
|
|
|
]);
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
DB::rollBack();
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => false,
|
|
|
|
|
'message' => '복제 중 오류가 발생했습니다: '.$e->getMessage(),
|
|
|
|
|
], 500);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-27 11:31:02 +09:00
|
|
|
/**
|
|
|
|
|
* 이미지 업로드
|
|
|
|
|
*/
|
|
|
|
|
public function uploadImage(Request $request): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
$request->validate([
|
|
|
|
|
'image' => 'required|image|max:5120', // 5MB
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$path = $request->file('image')->store('document-templates', 'public');
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => true,
|
|
|
|
|
'path' => $path,
|
|
|
|
|
'url' => asset('storage/'.$path),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 16:29:45 +09:00
|
|
|
/**
|
|
|
|
|
* 공통코드 그룹 조회 (JSON)
|
|
|
|
|
*/
|
|
|
|
|
public function getCommonCodes(string $group): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
$tenantId = session('selected_tenant_id');
|
|
|
|
|
|
|
|
|
|
$codes = \App\Models\Products\CommonCode::query()
|
|
|
|
|
->where('code_group', $group)
|
|
|
|
|
->where('is_active', true)
|
|
|
|
|
->where(function ($q) use ($tenantId) {
|
|
|
|
|
$q->whereNull('tenant_id');
|
|
|
|
|
if ($tenantId) {
|
|
|
|
|
$q->orWhere('tenant_id', $tenantId);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
->orderBy('sort_order')
|
|
|
|
|
->get(['code', 'name']);
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => true,
|
|
|
|
|
'data' => $codes,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-27 11:31:02 +09:00
|
|
|
/**
|
|
|
|
|
* 관계 데이터 저장
|
|
|
|
|
*/
|
|
|
|
|
private function saveRelations(DocumentTemplate $template, array $data, bool $deleteExisting = false): void
|
|
|
|
|
{
|
|
|
|
|
// 기존 데이터 삭제 (수정 시)
|
|
|
|
|
if ($deleteExisting) {
|
|
|
|
|
$template->approvalLines()->delete();
|
|
|
|
|
$template->basicFields()->delete();
|
|
|
|
|
// sections는 cascade로 items도 함께 삭제됨
|
|
|
|
|
$template->sections()->delete();
|
|
|
|
|
$template->columns()->delete();
|
2026-02-04 08:38:00 +09:00
|
|
|
$template->sectionFields()->delete();
|
|
|
|
|
// links는 cascade로 linkValues도 함께 삭제됨
|
|
|
|
|
$template->links()->delete();
|
2026-01-27 11:31:02 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 결재라인
|
|
|
|
|
if (! empty($data['approval_lines'])) {
|
|
|
|
|
foreach ($data['approval_lines'] as $index => $line) {
|
|
|
|
|
DocumentTemplateApprovalLine::create([
|
|
|
|
|
'template_id' => $template->id,
|
|
|
|
|
'name' => $line['name'] ?? '',
|
|
|
|
|
'dept' => $line['dept'] ?? '',
|
|
|
|
|
'role' => $line['role'] ?? '',
|
2026-02-03 10:33:48 +09:00
|
|
|
'user_id' => $line['user_id'] ?? null,
|
2026-01-27 11:31:02 +09:00
|
|
|
'sort_order' => $index,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 기본 필드
|
|
|
|
|
if (! empty($data['basic_fields'])) {
|
|
|
|
|
foreach ($data['basic_fields'] as $index => $field) {
|
|
|
|
|
DocumentTemplateBasicField::create([
|
|
|
|
|
'template_id' => $template->id,
|
|
|
|
|
'label' => $field['label'] ?? '',
|
|
|
|
|
'field_type' => $field['field_type'] ?? 'text',
|
|
|
|
|
'default_value' => $field['default_value'] ?? '',
|
|
|
|
|
'sort_order' => $index,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 섹션 및 항목
|
|
|
|
|
if (! empty($data['sections'])) {
|
|
|
|
|
foreach ($data['sections'] as $sIndex => $section) {
|
|
|
|
|
$newSection = DocumentTemplateSection::create([
|
|
|
|
|
'template_id' => $template->id,
|
|
|
|
|
'title' => $section['title'] ?? '',
|
|
|
|
|
'image_path' => $section['image_path'] ?? null,
|
|
|
|
|
'sort_order' => $sIndex,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if (! empty($section['items'])) {
|
|
|
|
|
foreach ($section['items'] as $iIndex => $item) {
|
|
|
|
|
DocumentTemplateSectionItem::create([
|
|
|
|
|
'section_id' => $newSection->id,
|
|
|
|
|
'category' => $item['category'] ?? '',
|
|
|
|
|
'item' => $item['item'] ?? '',
|
|
|
|
|
'standard' => $item['standard'] ?? '',
|
2026-02-02 16:29:45 +09:00
|
|
|
'tolerance' => $item['tolerance'] ?? null,
|
2026-02-02 20:37:06 +09:00
|
|
|
'standard_criteria' => $item['standard_criteria'] ?? null,
|
2026-01-27 11:31:02 +09:00
|
|
|
'method' => $item['method'] ?? '',
|
2026-02-02 16:29:45 +09:00
|
|
|
'measurement_type' => $item['measurement_type'] ?? null,
|
2026-02-02 20:37:06 +09:00
|
|
|
'frequency_n' => $item['frequency_n'] ?? null,
|
|
|
|
|
'frequency_c' => $item['frequency_c'] ?? null,
|
2026-01-27 11:31:02 +09:00
|
|
|
'frequency' => $item['frequency'] ?? '',
|
|
|
|
|
'regulation' => $item['regulation'] ?? '',
|
|
|
|
|
'sort_order' => $iIndex,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 컬럼
|
|
|
|
|
if (! empty($data['columns'])) {
|
|
|
|
|
foreach ($data['columns'] as $index => $column) {
|
|
|
|
|
DocumentTemplateColumn::create([
|
|
|
|
|
'template_id' => $template->id,
|
|
|
|
|
'label' => $column['label'] ?? '',
|
|
|
|
|
'width' => $column['width'] ?? '100px',
|
|
|
|
|
'column_type' => $column['column_type'] ?? 'text',
|
|
|
|
|
'group_name' => $column['group_name'] ?? null,
|
|
|
|
|
'sub_labels' => $column['sub_labels'] ?? null,
|
|
|
|
|
'sort_order' => $index,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-04 08:38:00 +09:00
|
|
|
|
|
|
|
|
// 검사 기준서 동적 필드 정의
|
|
|
|
|
if (! empty($data['section_fields'])) {
|
|
|
|
|
foreach ($data['section_fields'] as $index => $field) {
|
|
|
|
|
DocumentTemplateSectionField::create([
|
|
|
|
|
'template_id' => $template->id,
|
|
|
|
|
'field_key' => $field['field_key'] ?? '',
|
|
|
|
|
'label' => $field['label'] ?? '',
|
|
|
|
|
'field_type' => $field['field_type'] ?? 'text',
|
|
|
|
|
'options' => $field['options'] ?? null,
|
|
|
|
|
'width' => $field['width'] ?? '100px',
|
|
|
|
|
'is_required' => $field['is_required'] ?? false,
|
|
|
|
|
'sort_order' => $index,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 외부 키 매핑 + 연결 값
|
|
|
|
|
if (! empty($data['template_links'])) {
|
|
|
|
|
foreach ($data['template_links'] as $index => $link) {
|
|
|
|
|
$newLink = DocumentTemplateLink::create([
|
|
|
|
|
'template_id' => $template->id,
|
|
|
|
|
'link_key' => $link['link_key'] ?? '',
|
|
|
|
|
'label' => $link['label'] ?? '',
|
|
|
|
|
'link_type' => $link['link_type'] ?? 'single',
|
|
|
|
|
'source_table' => $link['source_table'] ?? '',
|
|
|
|
|
'search_params' => $link['search_params'] ?? null,
|
|
|
|
|
'display_fields' => $link['display_fields'] ?? null,
|
|
|
|
|
'is_required' => $link['is_required'] ?? false,
|
|
|
|
|
'sort_order' => $index,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// 연결 값 저장
|
|
|
|
|
if (! empty($link['values'])) {
|
|
|
|
|
foreach ($link['values'] as $vIndex => $value) {
|
|
|
|
|
DocumentTemplateLinkValue::create([
|
|
|
|
|
'template_id' => $template->id,
|
|
|
|
|
'link_id' => $newLink->id,
|
|
|
|
|
'linkable_id' => $value['linkable_id'] ?? $value['id'] ?? $value,
|
|
|
|
|
'sort_order' => $vIndex,
|
|
|
|
|
'created_at' => now(),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-27 11:31:02 +09:00
|
|
|
}
|
2026-02-04 08:38:00 +09:00
|
|
|
}
|