feat:문서관리 Phase 1.3~2.1 구현 (시드데이터, 복제, 문서생성)
- Phase 1.3: EGI/SUS 수입검사 시드 데이터 생성 (IncomingInspectionTemplateSeeder) - Phase 1.5: 양식 복제 기능 (duplicate API, 테이블 버튼, JS) - Phase 2.1: 문서 생성 보완 - 문서번호 카테고리별 prefix (IQC/PRD/SLS/PUR-YYMMDD-순번) - 결재라인 초기화 (template.approvalLines → document_approvals) - 기본필드 뷰 속성 수정 (field_type, Str::slug field_key) - store()에 DB 트랜잭션 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,9 +4,12 @@
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Documents\Document;
|
||||
use App\Models\Documents\DocumentApproval;
|
||||
use App\Models\Documents\DocumentData;
|
||||
use App\Models\DocumentTemplate;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class DocumentApiController extends Controller
|
||||
{
|
||||
@@ -86,37 +89,66 @@ public function store(Request $request): JsonResponse
|
||||
'data.*.field_value' => 'nullable|string',
|
||||
]);
|
||||
|
||||
// 문서 번호 생성
|
||||
$documentNo = $this->generateDocumentNo($tenantId, $request->template_id);
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$document = Document::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'template_id' => $request->template_id,
|
||||
'document_no' => $documentNo,
|
||||
'title' => $request->title,
|
||||
'status' => Document::STATUS_DRAFT,
|
||||
'created_by' => $userId,
|
||||
'updated_by' => $userId,
|
||||
]);
|
||||
// 문서 번호 생성
|
||||
$documentNo = $this->generateDocumentNo($tenantId, $request->template_id);
|
||||
|
||||
// 문서 데이터 저장
|
||||
if ($request->filled('data')) {
|
||||
foreach ($request->data as $item) {
|
||||
if (! empty($item['field_value'])) {
|
||||
DocumentData::create([
|
||||
$document = Document::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'template_id' => $request->template_id,
|
||||
'document_no' => $documentNo,
|
||||
'title' => $request->title,
|
||||
'status' => Document::STATUS_DRAFT,
|
||||
'created_by' => $userId,
|
||||
'updated_by' => $userId,
|
||||
]);
|
||||
|
||||
// 결재라인 초기화 (템플릿의 approvalLines 기반)
|
||||
$template = DocumentTemplate::with('approvalLines')->find($request->template_id);
|
||||
if ($template && $template->approvalLines->isNotEmpty()) {
|
||||
foreach ($template->approvalLines as $line) {
|
||||
DocumentApproval::create([
|
||||
'document_id' => $document->id,
|
||||
'field_key' => $item['field_key'],
|
||||
'field_value' => $item['field_value'],
|
||||
'user_id' => $userId,
|
||||
'step' => $line->sort_order + 1,
|
||||
'role' => $line->role ?? $line->name,
|
||||
'status' => DocumentApproval::STATUS_PENDING,
|
||||
'created_by' => $userId,
|
||||
'updated_by' => $userId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => '문서가 저장되었습니다.',
|
||||
'data' => $document->fresh(['template', 'data']),
|
||||
], 201);
|
||||
// 문서 데이터 저장
|
||||
if ($request->filled('data')) {
|
||||
foreach ($request->data as $item) {
|
||||
if (! empty($item['field_value'])) {
|
||||
DocumentData::create([
|
||||
'document_id' => $document->id,
|
||||
'field_key' => $item['field_key'],
|
||||
'field_value' => $item['field_value'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => '문서가 저장되었습니다.',
|
||||
'data' => $document->fresh(['template', 'data', 'approvals']),
|
||||
], 201);
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => '문서 생성 중 오류가 발생했습니다: '.$e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -200,27 +232,50 @@ public function destroy(int $id): JsonResponse
|
||||
|
||||
/**
|
||||
* 문서 번호 생성
|
||||
* 형식: {카테고리prefix}-{YYMMDD}-{순번}
|
||||
* 예: IQC-260131-01, PRD-260131-01
|
||||
*/
|
||||
private function generateDocumentNo(int $tenantId, int $templateId): string
|
||||
{
|
||||
$prefix = 'DOC';
|
||||
$date = now()->format('Ymd');
|
||||
$template = DocumentTemplate::find($templateId);
|
||||
$prefix = $this->getCategoryPrefix($template?->category);
|
||||
$date = now()->format('ymd');
|
||||
|
||||
$lastDocument = Document::where('tenant_id', $tenantId)
|
||||
->where('template_id', $templateId)
|
||||
->whereDate('created_at', now()->toDateString())
|
||||
->where('document_no', 'like', "{$prefix}-{$date}-%")
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
|
||||
$sequence = 1;
|
||||
if ($lastDocument) {
|
||||
// 마지막 문서 번호에서 시퀀스 추출
|
||||
$parts = explode('-', $lastDocument->document_no);
|
||||
if (count($parts) >= 3) {
|
||||
$sequence = (int) end($parts) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return sprintf('%s-%s-%04d', $prefix, $date, $sequence);
|
||||
return sprintf('%s-%s-%02d', $prefix, $date, $sequence);
|
||||
}
|
||||
|
||||
/**
|
||||
* 카테고리별 문서번호 prefix
|
||||
* 카테고리가 '품질/수입검사' 등 슬래시 포함 시 상위 카테고리 기준
|
||||
*/
|
||||
private function getCategoryPrefix(?string $category): string
|
||||
{
|
||||
if (! $category) {
|
||||
return 'DOC';
|
||||
}
|
||||
|
||||
// 상위 카테고리 추출 (슬래시 포함 시)
|
||||
$mainCategory = str_contains($category, '/') ? explode('/', $category)[0] : $category;
|
||||
|
||||
return match ($mainCategory) {
|
||||
'품질' => 'IQC',
|
||||
'생산' => 'PRD',
|
||||
'영업' => 'SLS',
|
||||
'구매' => 'PUR',
|
||||
default => 'DOC',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,6 +217,108 @@ public function toggleActive(int $id): JsonResponse
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 양식 복제
|
||||
*/
|
||||
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,
|
||||
'method' => $item->method,
|
||||
'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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 이미지 업로드
|
||||
*/
|
||||
|
||||
299
database/seeders/IncomingInspectionTemplateSeeder.php
Normal file
299
database/seeders/IncomingInspectionTemplateSeeder.php
Normal file
@@ -0,0 +1,299 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
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\Database\Seeder;
|
||||
|
||||
class IncomingInspectionTemplateSeeder extends Seeder
|
||||
{
|
||||
private int $tenantId = 1;
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$templates = $this->getTemplateDefinitions();
|
||||
|
||||
foreach ($templates as $def) {
|
||||
$this->cleanupExisting($def['name']);
|
||||
|
||||
$template = DocumentTemplate::create([
|
||||
'tenant_id' => $this->tenantId,
|
||||
'name' => $def['name'],
|
||||
'category' => '품질/수입검사',
|
||||
'title' => '수 입 검 사 성 적 서',
|
||||
'company_name' => '케이디산업',
|
||||
'footer_remark_label' => '부적합 내용',
|
||||
'footer_judgement_label' => '종합판정',
|
||||
'footer_judgement_options' => ['합격', '불합격'],
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$this->createApprovalLines($template->id);
|
||||
$this->createBasicFields($template->id);
|
||||
$this->createSection($template->id, $def['section_title'], $def['items']);
|
||||
$this->createColumns($template->id);
|
||||
|
||||
$this->command->info("✅ {$def['name']} (ID: {$template->id})");
|
||||
}
|
||||
}
|
||||
|
||||
private function getTemplateDefinitions(): array
|
||||
{
|
||||
return [
|
||||
// ─── EGI (전기아연도금강판) ───
|
||||
[
|
||||
'name' => 'EGI 수입검사 성적서',
|
||||
'section_title' => '전기 아연도금 강판 (KS D 3528, SECC) "EGI 절곡판"',
|
||||
'items' => [
|
||||
[
|
||||
'category' => '겉모양',
|
||||
'item' => '겉모양',
|
||||
'standard' => '사용상 해로울 결함이 없을 것',
|
||||
'method' => '육안검사',
|
||||
'frequency' => 'n=3, c=0',
|
||||
'regulation' => 'KS D 3528',
|
||||
],
|
||||
[
|
||||
'category' => '치수',
|
||||
'item' => '두께',
|
||||
'standard' => '0.8~1.0: ±0.07 / 1.0~1.25: ±0.08 / 1.25~1.6: ±0.10 / 1.6~2.0: ±0.12',
|
||||
'method' => '체크검사',
|
||||
'frequency' => 'n=3, c=0',
|
||||
'regulation' => 'KS D 3528',
|
||||
],
|
||||
[
|
||||
'category' => '치수',
|
||||
'item' => '너비',
|
||||
'standard' => '1250 미만: +7/-0',
|
||||
'method' => '체크검사',
|
||||
'frequency' => 'n=3, c=0',
|
||||
'regulation' => 'KS D 3528',
|
||||
],
|
||||
[
|
||||
'category' => '치수',
|
||||
'item' => '길이',
|
||||
'standard' => '~1250: +10/-0 / 2000~4000: +15/-0 / 4000~6000: +20/-0',
|
||||
'method' => '체크검사',
|
||||
'frequency' => 'n=3, c=0',
|
||||
'regulation' => 'KS D 3528',
|
||||
],
|
||||
[
|
||||
'category' => '기계적성질',
|
||||
'item' => '인장강도 (N/mm²)',
|
||||
'standard' => '270 이상',
|
||||
'method' => '공급업체 밀시트',
|
||||
'frequency' => '입고시',
|
||||
'regulation' => 'KS D 3528',
|
||||
],
|
||||
[
|
||||
'category' => '기계적성질',
|
||||
'item' => '연신율 (%)',
|
||||
'standard' => '0.6~1.0: 36이상 / 1.0~1.6: 37이상 / 1.6~2.3: 38이상',
|
||||
'method' => '공급업체 밀시트',
|
||||
'frequency' => '입고시',
|
||||
'regulation' => 'KS D 3528',
|
||||
],
|
||||
[
|
||||
'category' => '도금',
|
||||
'item' => '아연 최소 부착량 (g/m²)',
|
||||
'standard' => '한면 17 이상',
|
||||
'method' => '공급업체 밀시트',
|
||||
'frequency' => '입고시',
|
||||
'regulation' => 'KS F 4510',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
// ─── SUS (스테인리스강판) ───
|
||||
[
|
||||
'name' => 'SUS 수입검사 성적서',
|
||||
'section_title' => '냉간 압연 스테인리스 강판 (KS D 3698, STS304) "SUS 절곡판"',
|
||||
'items' => [
|
||||
[
|
||||
'category' => '겉모양',
|
||||
'item' => '겉모양',
|
||||
'standard' => '사용상 해로울 결함이 없을 것',
|
||||
'method' => '육안검사',
|
||||
'frequency' => 'n=3, c=0',
|
||||
'regulation' => 'KS D 3698',
|
||||
],
|
||||
[
|
||||
'category' => '치수',
|
||||
'item' => '두께',
|
||||
'standard' => '1.0~1.25: ±0.10 / 1.25~1.6: ±0.12',
|
||||
'method' => '체크검사',
|
||||
'frequency' => 'n=3, c=0',
|
||||
'regulation' => 'KS D 3698',
|
||||
],
|
||||
[
|
||||
'category' => '치수',
|
||||
'item' => '너비',
|
||||
'standard' => '1250 미만: +7/-0',
|
||||
'method' => '체크검사',
|
||||
'frequency' => 'n=3, c=0',
|
||||
'regulation' => 'KS D 3698',
|
||||
],
|
||||
[
|
||||
'category' => '치수',
|
||||
'item' => '길이',
|
||||
'standard' => '~3500: +10/-0 / 3500~6000: +20/-0',
|
||||
'method' => '체크검사',
|
||||
'frequency' => 'n=3, c=0',
|
||||
'regulation' => 'KS D 3698',
|
||||
],
|
||||
[
|
||||
'category' => '기계적성질',
|
||||
'item' => '항복강도 (N/mm²)',
|
||||
'standard' => '205 이상',
|
||||
'method' => '공급업체 밀시트',
|
||||
'frequency' => '입고시',
|
||||
'regulation' => 'KS D 3698',
|
||||
],
|
||||
[
|
||||
'category' => '기계적성질',
|
||||
'item' => '인장강도 (N/mm²)',
|
||||
'standard' => '520 이상',
|
||||
'method' => '공급업체 밀시트',
|
||||
'frequency' => '입고시',
|
||||
'regulation' => 'KS D 3698',
|
||||
],
|
||||
[
|
||||
'category' => '기계적성질',
|
||||
'item' => '연신율 (%)',
|
||||
'standard' => '40 이상',
|
||||
'method' => '공급업체 밀시트',
|
||||
'frequency' => '입고시',
|
||||
'regulation' => 'KS D 3698',
|
||||
],
|
||||
[
|
||||
'category' => '기계적성질',
|
||||
'item' => '경도 (HV)',
|
||||
'standard' => '200 이하',
|
||||
'method' => '공급업체 밀시트',
|
||||
'frequency' => '입고시',
|
||||
'regulation' => 'KS D 3698',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 결재라인: 담당 / 부서장 (5130 동일)
|
||||
*/
|
||||
private function createApprovalLines(int $templateId): void
|
||||
{
|
||||
$lines = [
|
||||
['name' => '담당', 'dept' => '품질', 'role' => '담당자', 'sort_order' => 1],
|
||||
['name' => '부서장', 'dept' => '품질', 'role' => '부서장', 'sort_order' => 2],
|
||||
];
|
||||
|
||||
foreach ($lines as $line) {
|
||||
DocumentTemplateApprovalLine::create(array_merge(
|
||||
['template_id' => $templateId],
|
||||
$line,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 기본정보 필드 (5130 공통)
|
||||
*/
|
||||
private function createBasicFields(int $templateId): void
|
||||
{
|
||||
$fields = [
|
||||
['label' => '품명', 'field_type' => 'text', 'sort_order' => 1],
|
||||
['label' => '규격 (두께*너비*길이)', 'field_type' => 'text', 'sort_order' => 2],
|
||||
['label' => '납품업체', 'field_type' => 'text', 'sort_order' => 3],
|
||||
['label' => '제조업체', 'field_type' => 'text', 'sort_order' => 4],
|
||||
['label' => '로트번호', 'field_type' => 'text', 'sort_order' => 5],
|
||||
['label' => '자재번호', 'field_type' => 'text', 'sort_order' => 6],
|
||||
['label' => '검사일자', 'field_type' => 'date', 'sort_order' => 7],
|
||||
['label' => '로트크기', 'field_type' => 'text', 'sort_order' => 8],
|
||||
['label' => '단위', 'field_type' => 'text', 'sort_order' => 9],
|
||||
['label' => '검사자', 'field_type' => 'text', 'sort_order' => 10],
|
||||
];
|
||||
|
||||
foreach ($fields as $field) {
|
||||
DocumentTemplateBasicField::create(array_merge(
|
||||
['template_id' => $templateId],
|
||||
$field,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 검사 기준서 섹션 + 항목
|
||||
*/
|
||||
private function createSection(int $templateId, string $title, array $items): void
|
||||
{
|
||||
$section = DocumentTemplateSection::create([
|
||||
'template_id' => $templateId,
|
||||
'title' => $title,
|
||||
'sort_order' => 1,
|
||||
]);
|
||||
|
||||
foreach ($items as $i => $item) {
|
||||
DocumentTemplateSectionItem::create(array_merge(
|
||||
['section_id' => $section->id, 'sort_order' => $i + 1],
|
||||
$item,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 데이터 테이블 컬럼 (5130 공통 구조)
|
||||
*/
|
||||
private function createColumns(int $templateId): void
|
||||
{
|
||||
$columns = [
|
||||
['label' => 'NO', 'column_type' => 'text', 'width' => '50px', 'sort_order' => 1],
|
||||
['label' => '검사항목', 'column_type' => 'text', 'width' => '120px', 'sort_order' => 2],
|
||||
['label' => '검사기준', 'column_type' => 'text', 'width' => '150px', 'sort_order' => 3],
|
||||
['label' => '검사방식', 'column_type' => 'text', 'width' => '100px', 'sort_order' => 4],
|
||||
['label' => '검사주기', 'column_type' => 'text', 'width' => '100px', 'sort_order' => 5],
|
||||
[
|
||||
'label' => '측정치',
|
||||
'column_type' => 'complex',
|
||||
'group_name' => '측정치',
|
||||
'sub_labels' => ['n1', 'n2', 'n3'],
|
||||
'width' => '240px',
|
||||
'sort_order' => 6,
|
||||
],
|
||||
['label' => '판정 (적/부)', 'column_type' => 'select', 'width' => '80px', 'sort_order' => 7],
|
||||
];
|
||||
|
||||
foreach ($columns as $col) {
|
||||
DocumentTemplateColumn::create(array_merge(
|
||||
['template_id' => $templateId],
|
||||
$col,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private function cleanupExisting(string $name): void
|
||||
{
|
||||
$existing = DocumentTemplate::where('tenant_id', $this->tenantId)
|
||||
->where('name', $name)
|
||||
->first();
|
||||
|
||||
if (! $existing) {
|
||||
return;
|
||||
}
|
||||
|
||||
DocumentTemplateColumn::where('template_id', $existing->id)->delete();
|
||||
$sections = DocumentTemplateSection::where('template_id', $existing->id)->get();
|
||||
foreach ($sections as $section) {
|
||||
DocumentTemplateSectionItem::where('section_id', $section->id)->delete();
|
||||
}
|
||||
DocumentTemplateSection::where('template_id', $existing->id)->delete();
|
||||
DocumentTemplateBasicField::where('template_id', $existing->id)->delete();
|
||||
DocumentTemplateApprovalLine::where('template_id', $existing->id)->delete();
|
||||
$existing->forceDelete();
|
||||
}
|
||||
}
|
||||
@@ -120,6 +120,35 @@ function initFilterForm() {
|
||||
});
|
||||
};
|
||||
|
||||
// 양식 복제
|
||||
window.duplicateTemplate = function(id, name) {
|
||||
const newName = prompt('복제할 양식 이름을 입력하세요:', name + ' (복사)');
|
||||
if (newName === null) return; // 취소
|
||||
|
||||
fetch(`/api/admin/document-templates/${id}/duplicate`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ name: newName })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showToast(data.message || '복제되었습니다.', 'success');
|
||||
htmx.trigger('#template-table', 'filterSubmit');
|
||||
} else {
|
||||
showToast(data.message || '복제에 실패했습니다.', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showToast('복제 중 오류가 발생했습니다.', 'error');
|
||||
console.error('Duplicate error:', error);
|
||||
});
|
||||
};
|
||||
|
||||
// 활성 토글
|
||||
window.toggleActive = function(id, buttonEl) {
|
||||
const btn = buttonEl || document.querySelector(`tr[data-template-id="${id}"] button[onclick*="toggleActive"]`);
|
||||
|
||||
@@ -65,6 +65,13 @@ class="text-gray-600 hover:text-blue-600 transition"
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
</a>
|
||||
<button onclick="duplicateTemplate({{ $template->id }}, '{{ addslashes($template->name) }}')"
|
||||
class="text-gray-600 hover:text-green-600 transition"
|
||||
title="복제">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button onclick="confirmDelete({{ $template->id }}, '{{ addslashes($template->name) }}')"
|
||||
class="text-gray-600 hover:text-red-600 transition"
|
||||
title="삭제">
|
||||
|
||||
@@ -77,47 +77,30 @@ class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:rin
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
@foreach($template->basicFields as $field)
|
||||
@php
|
||||
$fieldKey = \Illuminate\Support\Str::slug($field->label, '_');
|
||||
$savedValue = $document?->data->where('field_key', $fieldKey)->first()?->field_value ?? $field->default_value ?? '';
|
||||
@endphp
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||
{{ $field->label }}
|
||||
@if($field->is_required)
|
||||
<span class="text-red-500">*</span>
|
||||
@endif
|
||||
</label>
|
||||
@if($field->type === 'textarea')
|
||||
<textarea name="data[{{ $field->field_key }}]"
|
||||
@if($field->field_type === 'textarea')
|
||||
<textarea name="data[{{ $fieldKey }}]"
|
||||
class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500"
|
||||
rows="3"
|
||||
{{ $field->is_required ? 'required' : '' }}
|
||||
placeholder="{{ $field->placeholder ?? '' }}">{{ $document?->data->where('field_key', $field->field_key)->first()?->field_value ?? '' }}</textarea>
|
||||
@elseif($field->type === 'select' && $field->options)
|
||||
<select name="data[{{ $field->field_key }}]"
|
||||
class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500"
|
||||
{{ $field->is_required ? 'required' : '' }}>
|
||||
<option value="">선택하세요</option>
|
||||
@foreach($field->options as $option)
|
||||
<option value="{{ $option }}" {{ ($document?->data->where('field_key', $field->field_key)->first()?->field_value ?? '') === $option ? 'selected' : '' }}>
|
||||
{{ $option }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@elseif($field->type === 'date')
|
||||
<input type="date" name="data[{{ $field->field_key }}]"
|
||||
value="{{ $document?->data->where('field_key', $field->field_key)->first()?->field_value ?? '' }}"
|
||||
class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500"
|
||||
{{ $field->is_required ? 'required' : '' }}>
|
||||
@elseif($field->type === 'number')
|
||||
<input type="number" name="data[{{ $field->field_key }}]"
|
||||
value="{{ $document?->data->where('field_key', $field->field_key)->first()?->field_value ?? '' }}"
|
||||
class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500"
|
||||
{{ $field->is_required ? 'required' : '' }}
|
||||
placeholder="{{ $field->placeholder ?? '' }}">
|
||||
rows="3">{{ $savedValue }}</textarea>
|
||||
@elseif($field->field_type === 'date')
|
||||
<input type="date" name="data[{{ $fieldKey }}]"
|
||||
value="{{ $savedValue }}"
|
||||
class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500">
|
||||
@elseif($field->field_type === 'number')
|
||||
<input type="number" name="data[{{ $fieldKey }}]"
|
||||
value="{{ $savedValue }}"
|
||||
class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500">
|
||||
@else
|
||||
<input type="text" name="data[{{ $field->field_key }}]"
|
||||
value="{{ $document?->data->where('field_key', $field->field_key)->first()?->field_value ?? '' }}"
|
||||
class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500"
|
||||
{{ $field->is_required ? 'required' : '' }}
|
||||
placeholder="{{ $field->placeholder ?? '' }}">
|
||||
<input type="text" name="data[{{ $fieldKey }}]"
|
||||
value="{{ $savedValue }}"
|
||||
class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500">
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
@@ -125,12 +108,15 @@ class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:rin
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- 섹션 (테이블 형태) --}}
|
||||
{{-- 섹션 (테이블 형태 - Phase 2.2에서 구현) --}}
|
||||
@if($template->sections && $template->sections->count() > 0)
|
||||
@foreach($template->sections as $section)
|
||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ $section->name }}</h2>
|
||||
<p class="text-sm text-gray-500 mb-4">테이블 형태의 데이터는 추후 구현 예정입니다.</p>
|
||||
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ $section->title }}</h2>
|
||||
@if($section->image_path)
|
||||
<img src="{{ asset('storage/' . $section->image_path) }}" alt="{{ $section->title }}" class="max-w-full h-auto mb-4 rounded">
|
||||
@endif
|
||||
<p class="text-sm text-gray-400 italic">검사 데이터 테이블은 Phase 2.2에서 구현됩니다.</p>
|
||||
</div>
|
||||
@endforeach
|
||||
@endif
|
||||
@@ -194,15 +180,19 @@ class="px-6 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
alert(isCreate ? '문서가 저장되었습니다.' : '문서가 수정되었습니다.');
|
||||
window.location.href = '/documents';
|
||||
showToast(isCreate ? '문서가 저장되었습니다.' : '문서가 수정되었습니다.', 'success');
|
||||
if (isCreate && result.data?.id) {
|
||||
window.location.href = '/documents/' + result.data.id + '/edit';
|
||||
} else {
|
||||
window.location.href = '/documents';
|
||||
}
|
||||
} else {
|
||||
alert(result.message || '오류가 발생했습니다.');
|
||||
showToast(result.message || '오류가 발생했습니다.', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('오류가 발생했습니다.');
|
||||
showToast('오류가 발생했습니다.', 'error');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -788,6 +788,7 @@
|
||||
Route::put('/{id}', [DocumentTemplateApiController::class, 'update'])->name('update');
|
||||
Route::delete('/{id}', [DocumentTemplateApiController::class, 'destroy'])->name('destroy');
|
||||
Route::post('/{id}/toggle-active', [DocumentTemplateApiController::class, 'toggleActive'])->name('toggle-active');
|
||||
Route::post('/{id}/duplicate', [DocumentTemplateApiController::class, 'duplicate'])->name('duplicate');
|
||||
Route::post('/upload-image', [DocumentTemplateApiController::class, 'uploadImage'])->name('upload-image');
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user