From b14b991d1c3fbe56af4d5474e564c64e2f982cc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Thu, 5 Feb 2026 09:26:13 +0900 Subject: [PATCH] =?UTF-8?q?feat:=EA=B2=80=EC=82=AC=20=EA=B8=B0=EC=A4=80?= =?UTF-8?q?=EC=84=9C=20=EB=8F=99=EC=A0=81=20=ED=95=84=EB=93=9C=20+=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=20=ED=95=98=EC=9D=B4=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20+=20=EB=AF=B8=EB=A6=AC=EB=B3=B4=EA=B8=B0=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 문서 작성 시 연결 품목 규격(두께/너비/길이) 기반 자동 하이라이트 - 미리보기에서 field_values 동적 필드 데이터 정상 표시 - DocumentTemplateController에서 field_values 직렬화 추가 - DocumentController에 linkedItemSpecs 조회 로직 추가 - Item 모델 attributes JSON cast 추가 Co-Authored-By: Claude Opus 4.5 --- .../Api/Admin/DocumentApiController.php | 67 +- app/Http/Controllers/DocumentController.php | 42 ++ .../DocumentTemplateController.php | 25 +- app/Models/Items/Item.php | 1 + .../views/document-templates/edit.blade.php | 571 ++++++++++-------- resources/views/documents/edit.blade.php | 57 +- resources/views/documents/index.blade.php | 79 ++- 7 files changed, 562 insertions(+), 280 deletions(-) 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-
- -
+ +
-
- - -
-
- - -
-
- - +
+ + +
+
- +