diff --git a/app/Http/Controllers/Api/Admin/DocumentApiController.php b/app/Http/Controllers/Api/Admin/DocumentApiController.php new file mode 100644 index 00000000..149c393b --- /dev/null +++ b/app/Http/Controllers/Api/Admin/DocumentApiController.php @@ -0,0 +1,226 @@ +where('tenant_id', $tenantId) + ->orderBy('created_at', 'desc'); + + // 상태 필터 + if ($request->filled('status')) { + $query->where('status', $request->status); + } + + // 템플릿 필터 + if ($request->filled('template_id')) { + $query->where('template_id', $request->template_id); + } + + // 검색 + if ($request->filled('search')) { + $search = $request->search; + $query->where(function ($q) use ($search) { + $q->where('document_no', 'like', "%{$search}%") + ->orWhere('title', 'like', "%{$search}%"); + }); + } + + $documents = $query->paginate($request->input('per_page', 15)); + + return response()->json($documents); + } + + /** + * 문서 상세 조회 + */ + public function show(int $id): JsonResponse + { + $tenantId = session('selected_tenant_id'); + + $document = Document::with([ + 'template.approvalLines', + 'template.basicFields', + 'template.sections.items', + 'template.columns', + 'approvals.user', + 'data', + 'attachments.file', + 'creator', + 'updater', + ])->where('tenant_id', $tenantId)->findOrFail($id); + + return response()->json([ + 'success' => true, + 'data' => $document, + ]); + } + + /** + * 문서 생성 + */ + public function store(Request $request): JsonResponse + { + $tenantId = session('selected_tenant_id'); + $userId = auth()->id(); + + $request->validate([ + 'template_id' => 'required|exists:document_templates,id', + 'title' => 'required|string|max:255', + 'data' => 'nullable|array', + 'data.*.field_key' => 'required|string', + 'data.*.field_value' => 'nullable|string', + ]); + + // 문서 번호 생성 + $documentNo = $this->generateDocumentNo($tenantId, $request->template_id); + + $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, + ]); + + // 문서 데이터 저장 + 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'], + ]); + } + } + } + + return response()->json([ + 'success' => true, + 'message' => '문서가 저장되었습니다.', + 'data' => $document->fresh(['template', 'data']), + ], 201); + } + + /** + * 문서 수정 + */ + public function update(int $id, Request $request): JsonResponse + { + $tenantId = session('selected_tenant_id'); + $userId = auth()->id(); + + $document = Document::where('tenant_id', $tenantId)->findOrFail($id); + + // 작성중 또는 반려 상태에서만 수정 가능 + if (! in_array($document->status, [Document::STATUS_DRAFT, Document::STATUS_REJECTED])) { + return response()->json([ + 'success' => false, + 'message' => '현재 상태에서는 수정할 수 없습니다.', + ], 422); + } + + $request->validate([ + 'title' => 'sometimes|required|string|max:255', + 'data' => 'nullable|array', + 'data.*.field_key' => 'required|string', + 'data.*.field_value' => 'nullable|string', + ]); + + $document->update([ + 'title' => $request->input('title', $document->title), + 'updated_by' => $userId, + ]); + + // 문서 데이터 업데이트 + if ($request->has('data')) { + // 기존 데이터 삭제 + $document->data()->delete(); + + // 새 데이터 저장 + 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'], + ]); + } + } + } + + return response()->json([ + 'success' => true, + 'message' => '문서가 수정되었습니다.', + 'data' => $document->fresh(['template', 'data']), + ]); + } + + /** + * 문서 삭제 (소프트 삭제) + */ + public function destroy(int $id): JsonResponse + { + $tenantId = session('selected_tenant_id'); + + $document = Document::where('tenant_id', $tenantId)->findOrFail($id); + + // 작성중 상태에서만 삭제 가능 + if ($document->status !== Document::STATUS_DRAFT) { + return response()->json([ + 'success' => false, + 'message' => '작성중 상태의 문서만 삭제할 수 있습니다.', + ], 422); + } + + $document->delete(); + + return response()->json([ + 'success' => true, + 'message' => '문서가 삭제되었습니다.', + ]); + } + + /** + * 문서 번호 생성 + */ + private function generateDocumentNo(int $tenantId, int $templateId): string + { + $prefix = 'DOC'; + $date = now()->format('Ymd'); + + $lastDocument = Document::where('tenant_id', $tenantId) + ->where('template_id', $templateId) + ->whereDate('created_at', now()->toDateString()) + ->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); + } +} diff --git a/app/Http/Controllers/DocumentController.php b/app/Http/Controllers/DocumentController.php new file mode 100644 index 00000000..7c3db747 --- /dev/null +++ b/app/Http/Controllers/DocumentController.php @@ -0,0 +1,121 @@ +header('HX-Request')) { + return response('', 200)->header('HX-Redirect', route('documents.index')); + } + + $tenantId = session('selected_tenant_id'); + + // 템플릿 목록 (필터용) + $templates = $tenantId + ? DocumentTemplate::where(function ($q) use ($tenantId) { + $q->whereNull('tenant_id')->orWhere('tenant_id', $tenantId); + })->where('is_active', true)->orderBy('name')->get() + : collect(); + + return view('documents.index', [ + 'templates' => $templates, + 'statuses' => Document::STATUS_LABELS, + ]); + } + + /** + * 문서 생성 페이지 + */ + public function create(Request $request): View + { + $tenantId = session('selected_tenant_id'); + $templateId = $request->query('template_id'); + + // 템플릿 목록 + $templates = $tenantId + ? DocumentTemplate::where(function ($q) use ($tenantId) { + $q->whereNull('tenant_id')->orWhere('tenant_id', $tenantId); + })->where('is_active', true)->orderBy('name')->get() + : collect(); + + // 선택된 템플릿 + $template = $templateId + ? DocumentTemplate::with(['approvalLines', 'basicFields', 'sections.items', 'columns'])->find($templateId) + : null; + + return view('documents.edit', [ + 'document' => null, + 'template' => $template, + 'templates' => $templates, + 'isCreate' => true, + ]); + } + + /** + * 문서 수정 페이지 + */ + public function edit(int $id): View + { + $tenantId = session('selected_tenant_id'); + + $document = Document::with([ + 'template.approvalLines', + 'template.basicFields', + 'template.sections.items', + 'template.columns', + 'approvals.user', + 'data', + 'attachments.file', + 'creator', + ])->where('tenant_id', $tenantId)->findOrFail($id); + + // 템플릿 목록 (변경용) + $templates = $tenantId + ? DocumentTemplate::where(function ($q) use ($tenantId) { + $q->whereNull('tenant_id')->orWhere('tenant_id', $tenantId); + })->where('is_active', true)->orderBy('name')->get() + : collect(); + + return view('documents.edit', [ + 'document' => $document, + 'template' => $document->template, + 'templates' => $templates, + 'isCreate' => false, + ]); + } + + /** + * 문서 상세 페이지 (읽기 전용) + */ + public function show(int $id): View + { + $tenantId = session('selected_tenant_id'); + + $document = Document::with([ + 'template.approvalLines', + 'template.basicFields', + 'template.sections.items', + 'template.columns', + 'approvals.user', + 'data', + 'attachments.file', + 'creator', + 'updater', + ])->where('tenant_id', $tenantId)->findOrFail($id); + + return view('documents.show', [ + 'document' => $document, + ]); + } +} diff --git a/app/Models/Documents/Document.php b/app/Models/Documents/Document.php new file mode 100644 index 00000000..d6a2bb3e --- /dev/null +++ b/app/Models/Documents/Document.php @@ -0,0 +1,178 @@ + '작성중', + self::STATUS_PENDING => '결재중', + self::STATUS_APPROVED => '승인', + self::STATUS_REJECTED => '반려', + self::STATUS_CANCELLED => '취소', + ]; + + protected $fillable = [ + 'tenant_id', + 'template_id', + 'document_no', + 'title', + 'status', + 'linkable_type', + 'linkable_id', + 'submitted_at', + 'completed_at', + 'created_by', + 'updated_by', + 'deleted_by', + ]; + + protected $casts = [ + 'submitted_at' => 'datetime', + 'completed_at' => 'datetime', + ]; + + protected $attributes = [ + 'status' => self::STATUS_DRAFT, + ]; + + // ========================================================================= + // Relationships + // ========================================================================= + + public function template(): BelongsTo + { + return $this->belongsTo(DocumentTemplate::class, 'template_id'); + } + + public function approvals(): HasMany + { + return $this->hasMany(DocumentApproval::class)->orderBy('step'); + } + + public function data(): HasMany + { + return $this->hasMany(DocumentData::class); + } + + public function attachments(): HasMany + { + return $this->hasMany(DocumentAttachment::class); + } + + public function linkable(): MorphTo + { + return $this->morphTo(); + } + + public function creator(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by'); + } + + public function updater(): BelongsTo + { + return $this->belongsTo(User::class, 'updated_by'); + } + + // ========================================================================= + // Accessors + // ========================================================================= + + public function getStatusLabelAttribute(): string + { + return self::STATUS_LABELS[$this->status] ?? $this->status; + } + + public function getStatusColorAttribute(): string + { + return match ($this->status) { + self::STATUS_DRAFT => 'gray', + self::STATUS_PENDING => 'yellow', + self::STATUS_APPROVED => 'green', + self::STATUS_REJECTED => 'red', + self::STATUS_CANCELLED => 'gray', + default => 'gray', + }; + } + + // ========================================================================= + // Scopes + // ========================================================================= + + public function scopeStatus($query, string $status) + { + return $query->where('status', $status); + } + + public function scopeDraft($query) + { + return $query->where('status', self::STATUS_DRAFT); + } + + public function scopePending($query) + { + return $query->where('status', self::STATUS_PENDING); + } + + // ========================================================================= + // Helper Methods + // ========================================================================= + + public function isDraft(): bool + { + return $this->status === self::STATUS_DRAFT; + } + + public function isPending(): bool + { + return $this->status === self::STATUS_PENDING; + } + + public function isApproved(): bool + { + return $this->status === self::STATUS_APPROVED; + } + + public function isRejected(): bool + { + return $this->status === self::STATUS_REJECTED; + } + + public function canEdit(): bool + { + return in_array($this->status, [self::STATUS_DRAFT, self::STATUS_REJECTED]); + } +} diff --git a/app/Models/Documents/DocumentApproval.php b/app/Models/Documents/DocumentApproval.php new file mode 100644 index 00000000..a9899c00 --- /dev/null +++ b/app/Models/Documents/DocumentApproval.php @@ -0,0 +1,94 @@ + '대기', + self::STATUS_APPROVED => '승인', + self::STATUS_REJECTED => '반려', + ]; + + protected $fillable = [ + 'document_id', + 'user_id', + 'step', + 'role', + 'status', + 'comment', + 'acted_at', + 'created_by', + 'updated_by', + ]; + + protected $casts = [ + 'step' => 'integer', + 'acted_at' => 'datetime', + ]; + + // ========================================================================= + // Relationships + // ========================================================================= + + public function document(): BelongsTo + { + return $this->belongsTo(Document::class); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + // ========================================================================= + // Accessors + // ========================================================================= + + public function getStatusLabelAttribute(): string + { + return self::STATUS_LABELS[$this->status] ?? $this->status; + } + + public function getStatusColorAttribute(): string + { + return match ($this->status) { + self::STATUS_PENDING => 'yellow', + self::STATUS_APPROVED => 'green', + self::STATUS_REJECTED => 'red', + default => 'gray', + }; + } + + // ========================================================================= + // Helper Methods + // ========================================================================= + + public function isPending(): bool + { + return $this->status === self::STATUS_PENDING; + } + + public function isApproved(): bool + { + return $this->status === self::STATUS_APPROVED; + } + + public function isRejected(): bool + { + return $this->status === self::STATUS_REJECTED; + } +} diff --git a/app/Models/Documents/DocumentAttachment.php b/app/Models/Documents/DocumentAttachment.php new file mode 100644 index 00000000..0509185a --- /dev/null +++ b/app/Models/Documents/DocumentAttachment.php @@ -0,0 +1,69 @@ + '일반', + self::TYPE_SIGNATURE => '서명', + self::TYPE_IMAGE => '이미지', + self::TYPE_REFERENCE => '참고자료', + ]; + + protected $fillable = [ + 'document_id', + 'file_id', + 'attachment_type', + 'description', + 'created_by', + ]; + + protected $attributes = [ + 'attachment_type' => self::TYPE_GENERAL, + ]; + + // ========================================================================= + // Relationships + // ========================================================================= + + public function document(): BelongsTo + { + return $this->belongsTo(Document::class); + } + + public function file(): BelongsTo + { + return $this->belongsTo(File::class); + } + + public function creator(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by'); + } + + // ========================================================================= + // Accessors + // ========================================================================= + + public function getTypeLabelAttribute(): string + { + return self::TYPE_LABELS[$this->attachment_type] ?? $this->attachment_type; + } +} diff --git a/app/Models/Documents/DocumentData.php b/app/Models/Documents/DocumentData.php new file mode 100644 index 00000000..6421ac97 --- /dev/null +++ b/app/Models/Documents/DocumentData.php @@ -0,0 +1,47 @@ + 'integer', + ]; + + // ========================================================================= + // Relationships + // ========================================================================= + + public function document(): BelongsTo + { + return $this->belongsTo(Document::class); + } + + // ========================================================================= + // Scopes + // ========================================================================= + + public function scopeForSection($query, int $sectionId) + { + return $query->where('section_id', $sectionId); + } + + public function scopeForField($query, string $fieldKey) + { + return $query->where('field_key', $fieldKey); + } +} diff --git a/resources/views/documents/edit.blade.php b/resources/views/documents/edit.blade.php new file mode 100644 index 00000000..7f6c4671 --- /dev/null +++ b/resources/views/documents/edit.blade.php @@ -0,0 +1,211 @@ +@extends('layouts.app') + +@section('title', $isCreate ? '새 문서 작성' : '문서 수정') + +@section('content') +
+ {{-- 헤더 --}} +
+
+

{{ $isCreate ? '새 문서 작성' : '문서 수정' }}

+

+ @if($document) + {{ $document->document_no }} - {{ $document->title }} + @else + 템플릿을 선택하여 문서를 작성합니다. + @endif +

+
+ + + + + 목록으로 + +
+ + {{-- 템플릿 선택 (생성 시) --}} + @if($isCreate && !$template) +
+

템플릿 선택

+
+ @forelse($templates as $tpl) + +

{{ $tpl->name }}

+

{{ $tpl->category }}

+
+ @empty +

사용 가능한 템플릿이 없습니다.

+ @endforelse +
+
+ @endif + + {{-- 문서 폼 --}} + @if($template) +
+ @csrf + + + {{-- 기본 정보 --}} +
+

기본 정보

+ +
+
+ + +
+ +
+ + +
+
+
+ + {{-- 기본 필드 --}} + @if($template->basicFields && $template->basicFields->count() > 0) +
+

{{ $template->title ?? '문서 정보' }}

+ +
+ @foreach($template->basicFields as $field) +
+ + @if($field->type === 'textarea') + + @elseif($field->type === 'select' && $field->options) + + @elseif($field->type === 'date') + is_required ? 'required' : '' }}> + @elseif($field->type === 'number') + is_required ? 'required' : '' }} + placeholder="{{ $field->placeholder ?? '' }}"> + @else + is_required ? 'required' : '' }} + placeholder="{{ $field->placeholder ?? '' }}"> + @endif +
+ @endforeach +
+
+ @endif + + {{-- 섹션 (테이블 형태) --}} + @if($template->sections && $template->sections->count() > 0) + @foreach($template->sections as $section) +
+

{{ $section->name }}

+

테이블 형태의 데이터는 추후 구현 예정입니다.

+
+ @endforeach + @endif + + {{-- 버튼 --}} +
+ + 취소 + + +
+
+ @endif +
+@endsection + +@push('scripts') + +@endpush \ No newline at end of file diff --git a/resources/views/documents/index.blade.php b/resources/views/documents/index.blade.php new file mode 100644 index 00000000..41798958 --- /dev/null +++ b/resources/views/documents/index.blade.php @@ -0,0 +1,240 @@ +@extends('layouts.app') + +@section('title', '문서 관리') + +@section('content') +
+ {{-- 헤더 --}} +
+
+

문서 관리

+

작성된 문서를 관리합니다.

+
+ + + + + 새 문서 작성 + +
+ + {{-- 필터 --}} +
+
+ {{-- 상태 필터 --}} +
+ + +
+ + {{-- 템플릿 필터 --}} +
+ + +
+ + {{-- 검색 --}} +
+ + +
+ + {{-- 버튼 --}} +
+ + +
+
+
+ + {{-- 문서 목록 --}} +
+
+ + + + + + + + + + + + + + {{-- HTMX로 로드 --}} + +
문서번호제목템플릿상태작성자작성일관리
+
+ + {{-- 페이지네이션 --}} + +
+
+@endsection + +@push('scripts') + +@endpush \ No newline at end of file diff --git a/resources/views/documents/show.blade.php b/resources/views/documents/show.blade.php new file mode 100644 index 00000000..4dcf9e2a --- /dev/null +++ b/resources/views/documents/show.blade.php @@ -0,0 +1,200 @@ +@extends('layouts.app') + +@section('title', '문서 상세') + +@section('content') +
+ {{-- 헤더 --}} +
+
+

문서 상세

+

{{ $document->document_no }} - {{ $document->title }}

+
+
+ @if($document->status === 'DRAFT' || $document->status === 'REJECTED') + + + + + 수정 + + @endif + + + + + 목록으로 + +
+
+ + {{-- 문서 정보 --}} +
+ {{-- 메인 컨텐츠 --}} +
+ {{-- 기본 정보 --}} +
+

기본 정보

+ +
+
+
문서번호
+
{{ $document->document_no }}
+
+
+
템플릿
+
{{ $document->template->name ?? '-' }}
+
+
+
제목
+
{{ $document->title }}
+
+
+
상태
+
+ + {{ $document->status_label }} + +
+
+
+
작성자
+
{{ $document->creator->name ?? '-' }}
+
+
+
작성일
+
{{ $document->created_at?->format('Y-m-d H:i') ?? '-' }}
+
+ @if($document->updated_at && $document->updated_at->ne($document->created_at)) +
+
수정자
+
{{ $document->updater->name ?? '-' }}
+
+
+
수정일
+
{{ $document->updated_at?->format('Y-m-d H:i') ?? '-' }}
+
+ @endif +
+
+ + {{-- 기본 필드 데이터 --}} + @if($document->template?->basicFields && $document->template->basicFields->count() > 0) +
+

{{ $document->template->title ?? '문서 정보' }}

+ +
+ @foreach($document->template->basicFields as $field) + @php + $fieldData = $document->data->where('field_key', $field->field_key)->first(); + $value = $fieldData?->field_value ?? '-'; + @endphp +
+
{{ $field->label }}
+
{{ $value }}
+
+ @endforeach +
+
+ @endif + + {{-- 섹션 데이터 (테이블) --}} + @if($document->template?->sections && $document->template->sections->count() > 0) + @foreach($document->template->sections as $section) +
+

{{ $section->name }}

+

테이블 형태의 데이터 표시는 추후 구현 예정입니다.

+
+ @endforeach + @endif + + {{-- 첨부파일 --}} + @if($document->attachments && $document->attachments->count() > 0) +
+

첨부파일

+ +
    + @foreach($document->attachments as $attachment) +
  • +
    + + + +
    +

    {{ $attachment->file->original_name ?? '파일명 없음' }}

    +

    + {{ $attachment->type_label }} · + {{ $attachment->file ? number_format($attachment->file->size / 1024, 1) . ' KB' : '-' }} +

    +
    +
    + @if($attachment->file) + + 다운로드 + + @endif +
  • + @endforeach +
+
+ @endif +
+ + {{-- 사이드바 --}} +
+ {{-- 결재 현황 --}} +
+

결재 현황

+ + @if($document->approvals && $document->approvals->count() > 0) +
    + @foreach($document->approvals->sortBy('step_order') as $approval) +
  1. + + @if($approval->status === 'APPROVED') + + + + @elseif($approval->status === 'REJECTED') + + + + @else + {{ $approval->step_order }} + @endif + +
    +

    {{ $approval->user->name ?? '미지정' }}

    +

    {{ $approval->step_name }}

    + @if($approval->approved_at) +

    {{ $approval->approved_at->format('Y-m-d H:i') }}

    + @endif + @if($approval->comment) +

    {{ $approval->comment }}

    + @endif +
    +
  2. + @endforeach +
+ @else +

결재선이 설정되지 않았습니다.

+ @endif +
+ + {{-- 문서 이력 --}} +
+

문서 이력

+

문서 이력 기능은 추후 구현 예정입니다.

+
+
+
+
+@endsection \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index ce1b9b8c..8d060e20 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,11 +1,12 @@ name('upload-image'); }); +/* +|-------------------------------------------------------------------------- +| 문서 관리 API +|-------------------------------------------------------------------------- +*/ +Route::middleware(['web', 'auth', 'hq.member'])->prefix('admin/documents')->name('api.admin.documents.')->group(function () { + Route::get('/', [DocumentApiController::class, 'index'])->name('index'); + Route::post('/', [DocumentApiController::class, 'store'])->name('store'); + Route::get('/{id}', [DocumentApiController::class, 'show'])->name('show'); + Route::patch('/{id}', [DocumentApiController::class, 'update'])->name('update'); + Route::delete('/{id}', [DocumentApiController::class, 'destroy'])->name('destroy'); +}); + /* |-------------------------------------------------------------------------- | 카테고리 관리 API diff --git a/routes/web.php b/routes/web.php index 11bc1aeb..418eb3d9 100644 --- a/routes/web.php +++ b/routes/web.php @@ -6,17 +6,21 @@ use App\Http\Controllers\AuditLogController; use App\Http\Controllers\Auth\LoginController; use App\Http\Controllers\BoardController; +use App\Http\Controllers\CategoryController; +use App\Http\Controllers\CommonCodeController; use App\Http\Controllers\CustomerCenterController; use App\Http\Controllers\DailyLogController; use App\Http\Controllers\DepartmentController; use App\Http\Controllers\DevTools\ApiExplorerController; use App\Http\Controllers\DevTools\FlowTesterController; +use App\Http\Controllers\DocumentController; +use App\Http\Controllers\DocumentTemplateController; use App\Http\Controllers\FcmController; use App\Http\Controllers\ItemFieldController; use App\Http\Controllers\Lab\AIController; -use App\Http\Controllers\Lab\ManagementController; use App\Http\Controllers\Lab\StrategyController; use App\Http\Controllers\MenuController; +use App\Http\Controllers\MenuSyncController; use App\Http\Controllers\PermissionController; use App\Http\Controllers\PostController; use App\Http\Controllers\ProfileController; @@ -27,10 +31,6 @@ use App\Http\Controllers\System\AiConfigController; use App\Http\Controllers\TenantController; use App\Http\Controllers\TenantSettingController; -use App\Http\Controllers\CommonCodeController; -use App\Http\Controllers\DocumentTemplateController; -use App\Http\Controllers\CategoryController; -use App\Http\Controllers\MenuSyncController; use App\Http\Controllers\UserController; use Illuminate\Support\Facades\Route; @@ -319,6 +319,14 @@ Route::get('/{id}/edit', [DocumentTemplateController::class, 'edit'])->name('edit'); }); + // 문서 관리 + Route::prefix('documents')->name('documents.')->group(function () { + Route::get('/', [DocumentController::class, 'index'])->name('index'); + Route::get('/create', [DocumentController::class, 'create'])->name('create'); + Route::get('/{id}', [DocumentController::class, 'show'])->whereNumber('id')->name('show'); + Route::get('/{id}/edit', [DocumentController::class, 'edit'])->whereNumber('id')->name('edit'); + }); + // AI 설정 관리 Route::prefix('system/ai-config')->name('system.ai-config.')->group(function () { Route::get('/', [AiConfigController::class, 'index'])->name('index'); @@ -629,6 +637,7 @@ if (request()->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('finance.account-transactions')); } + return view('finance.account-transactions'); })->name('account-transactions'); @@ -641,12 +650,13 @@ }); // 기존 fund-schedule URL 리다이렉트 (호환성) - Route::get('/fund-schedule', fn() => redirect()->route('finance.fund-schedules.index'))->name('fund-schedule'); + Route::get('/fund-schedule', fn () => redirect()->route('finance.fund-schedules.index'))->name('fund-schedule'); Route::get('/daily-fund', function () { // HTMX 요청이면 전체 페이지로 리다이렉트 (React 스크립트 로딩을 위해) if (request()->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('finance.daily-fund')); } + return view('finance.daily-fund'); })->name('daily-fund'); @@ -655,12 +665,14 @@ if (request()->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('finance.corporate-cards')); } + return view('finance.corporate-cards'); })->name('corporate-cards'); Route::get('/card-transactions', function () { if (request()->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('finance.card-transactions')); } + return view('finance.card-transactions'); })->name('card-transactions'); @@ -669,12 +681,14 @@ if (request()->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('finance.income')); } + return view('finance.income'); })->name('income'); Route::get('/expense', function () { if (request()->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('finance.expense')); } + return view('finance.expense'); })->name('expense'); @@ -683,12 +697,14 @@ if (request()->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('finance.sales')); } + return view('finance.sales'); })->name('sales'); Route::get('/purchase', function () { if (request()->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('finance.purchase')); } + return view('finance.purchase'); })->name('purchase'); @@ -697,24 +713,28 @@ if (request()->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('finance.sales-commission')); } + return view('finance.sales-commission'); })->name('sales-commission'); Route::get('/consulting-fee', function () { if (request()->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('finance.consulting-fee')); } + return view('finance.consulting-fee'); })->name('consulting-fee'); Route::get('/customer-settlement', function () { if (request()->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('finance.customer-settlement')); } + return view('finance.customer-settlement'); })->name('customer-settlement'); Route::get('/subscription', function () { if (request()->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('finance.subscription')); } + return view('finance.subscription'); })->name('subscription'); @@ -723,12 +743,14 @@ if (request()->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('finance.corporate-vehicles')); } + return view('finance.corporate-vehicles'); })->name('corporate-vehicles'); Route::get('/vehicle-maintenance', function () { if (request()->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('finance.vehicle-maintenance')); } + return view('finance.vehicle-maintenance'); })->name('vehicle-maintenance'); @@ -737,12 +759,14 @@ if (request()->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('finance.customers')); } + return view('finance.customers'); })->name('customers'); Route::get('/partners', function () { if (request()->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('finance.partners')); } + return view('finance.partners'); })->name('partners'); @@ -751,12 +775,14 @@ if (request()->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('finance.receivables')); } + return view('finance.receivables'); })->name('receivables'); Route::get('/payables', function () { if (request()->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('finance.payables')); } + return view('finance.payables'); })->name('payables'); @@ -765,12 +791,14 @@ if (request()->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('finance.refunds')); } + return view('finance.refunds'); })->name('refunds'); Route::get('/vat', function () { if (request()->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('finance.vat')); } + return view('finance.vat'); })->name('vat'); });