Files
sam-manage/app/Http/Controllers/Api/Admin/DocumentTemplateApiController.php
권혁성 623d6992f4 feat:검사주기(n/c값) 및 검사기준(standard_criteria) 기능 추가
- 검사주기 입력을 n값, c값, 텍스트 3개로 분리
- standard_criteria JSON으로 구조화된 비교기준 저장 (min/max + 이상/초과/이하/미만)
- 미리보기 측정치 셀 수를 frequency_n 기반 동적 렌더링
- 그룹 항목 미리보기에서 측정치/검사방식/주기/판정 행별 개별 표시
- ID 비교 === → == 수정 (문자열/숫자 타입 불일치 버그)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 20:37:06 +09:00

463 lines
17 KiB
PHP

<?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;
use App\Models\DocumentTemplateSection;
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']);
// 선택된 테넌트 필터
$tenantId = session('selected_tenant_id');
if ($tenantId) {
$query->where('tenant_id', $tenantId);
}
// 검색
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);
}
// 활성 상태 필터
if ($request->filled('is_active')) {
$query->where('is_active', $request->boolean('is_active'));
}
$templates = $query->orderBy('updated_at', 'desc')
->paginate($request->input('per_page', 10));
return view('document-templates.partials.table', compact('templates'));
}
/**
* 단일 조회
*/
public function show(int $id): JsonResponse
{
$template = DocumentTemplate::with([
'approvalLines',
'basicFields',
'sections.items',
'columns',
])->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',
// 관계 데이터
'approval_lines' => 'nullable|array',
'basic_fields' => 'nullable|array',
'sections' => 'nullable|array',
'columns' => 'nullable|array',
]);
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,
]);
// 관계 데이터 저장
$this->saveRelations($template, $validated);
DB::commit();
return response()->json([
'success' => true,
'message' => '문서양식이 생성되었습니다.',
'data' => $template->load(['approvalLines', 'basicFields', 'sections.items', 'columns']),
]);
} 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',
// 관계 데이터
'approval_lines' => 'nullable|array',
'basic_fields' => 'nullable|array',
'sections' => 'nullable|array',
'columns' => 'nullable|array',
]);
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,
]);
// 관계 데이터 저장 (기존 데이터 삭제 후 재생성)
$this->saveRelations($template, $validated, true);
DB::commit();
return response()->json([
'success' => true,
'message' => '문서양식이 수정되었습니다.',
'data' => $template->fresh(['approvalLines', 'basicFields', 'sections.items', 'columns']),
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'success' => false,
'message' => '수정 중 오류가 발생했습니다: '.$e->getMessage(),
], 500);
}
}
/**
* 삭제
*/
public function destroy(int $id): JsonResponse
{
$template = DocumentTemplate::findOrFail($id);
$template->delete();
return response()->json([
'success' => true,
'message' => '문서양식이 삭제되었습니다.',
]);
}
/**
* 활성 상태 토글
*/
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,
]);
}
/**
* 양식 복제
*/
public function duplicate(Request $request, int $id): JsonResponse
{
$source = DocumentTemplate::with([
'approvalLines',
'basicFields',
'sections.items',
'columns',
])->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,
]);
foreach ($source->approvalLines as $line) {
DocumentTemplateApprovalLine::create([
'template_id' => $newTemplate->id,
'name' => $line->name,
'dept' => $line->dept,
'role' => $line->role,
'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,
'tolerance' => $item->tolerance,
'standard_criteria' => $item->standard_criteria,
'method' => $item->method,
'measurement_type' => $item->measurement_type,
'frequency_n' => $item->frequency_n,
'frequency_c' => $item->frequency_c,
'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,
]);
}
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);
}
}
/**
* 이미지 업로드
*/
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),
]);
}
/**
* 공통코드 그룹 조회 (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,
]);
}
/**
* 관계 데이터 저장
*/
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();
}
// 결재라인
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'] ?? '',
'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'] ?? '',
'tolerance' => $item['tolerance'] ?? null,
'standard_criteria' => $item['standard_criteria'] ?? null,
'method' => $item['method'] ?? '',
'measurement_type' => $item['measurement_type'] ?? null,
'frequency_n' => $item['frequency_n'] ?? null,
'frequency_c' => $item['frequency_c'] ?? null,
'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,
]);
}
}
}
}