diff --git a/app/Http/Controllers/System/HolidayController.php b/app/Http/Controllers/System/HolidayController.php new file mode 100644 index 00000000..812b424a --- /dev/null +++ b/app/Http/Controllers/System/HolidayController.php @@ -0,0 +1,154 @@ +header('HX-Request')) { + return response('', 200)->header('HX-Redirect', route('system.holidays.index')); + } + + return view('system.holidays.index'); + } + + public function list(Request $request): JsonResponse + { + $tenantId = session('selected_tenant_id', 1); + $year = $request->input('year', now()->year); + + $holidays = Holiday::forTenant($tenantId) + ->forYear($year) + ->orderBy('start_date') + ->get() + ->map(function ($h) { + return [ + 'id' => $h->id, + 'startDate' => $h->start_date->format('Y-m-d'), + 'endDate' => $h->end_date->format('Y-m-d'), + 'name' => $h->name, + 'type' => $h->type, + 'isRecurring' => $h->is_recurring, + 'memo' => $h->memo, + 'days' => $h->start_date->diffInDays($h->end_date) + 1, + ]; + }); + + return response()->json([ + 'success' => true, + 'data' => $holidays, + ]); + } + + public function store(Request $request): JsonResponse + { + $request->validate([ + 'name' => 'required|string|max:100', + 'startDate' => 'required|date', + 'endDate' => 'required|date|after_or_equal:startDate', + 'type' => 'required|in:public,company,alternative,temporary', + 'isRecurring' => 'boolean', + 'memo' => 'nullable|string|max:500', + ]); + + $tenantId = session('selected_tenant_id', 1); + + $holiday = Holiday::create([ + 'tenant_id' => $tenantId, + 'start_date' => $request->input('startDate'), + 'end_date' => $request->input('endDate'), + 'name' => $request->input('name'), + 'type' => $request->input('type', 'public'), + 'is_recurring' => $request->input('isRecurring', false), + 'memo' => $request->input('memo'), + ]); + + return response()->json([ + 'success' => true, + 'message' => '휴일이 등록되었습니다.', + 'data' => ['id' => $holiday->id], + ]); + } + + public function update(Request $request, int $id): JsonResponse + { + $tenantId = session('selected_tenant_id', 1); + $holiday = Holiday::forTenant($tenantId)->findOrFail($id); + + $request->validate([ + 'name' => 'required|string|max:100', + 'startDate' => 'required|date', + 'endDate' => 'required|date|after_or_equal:startDate', + 'type' => 'required|in:public,company,alternative,temporary', + 'isRecurring' => 'boolean', + 'memo' => 'nullable|string|max:500', + ]); + + $holiday->update([ + 'start_date' => $request->input('startDate'), + 'end_date' => $request->input('endDate'), + 'name' => $request->input('name'), + 'type' => $request->input('type'), + 'is_recurring' => $request->input('isRecurring', false), + 'memo' => $request->input('memo'), + ]); + + return response()->json([ + 'success' => true, + 'message' => '휴일이 수정되었습니다.', + ]); + } + + public function destroy(int $id): JsonResponse + { + $tenantId = session('selected_tenant_id', 1); + $holiday = Holiday::forTenant($tenantId)->findOrFail($id); + $holiday->delete(); + + return response()->json([ + 'success' => true, + 'message' => '휴일이 삭제되었습니다.', + ]); + } + + public function bulkStore(Request $request): JsonResponse + { + $request->validate([ + 'holidays' => 'required|array|min:1', + 'holidays.*.name' => 'required|string|max:100', + 'holidays.*.startDate' => 'required|date', + 'holidays.*.endDate' => 'required|date|after_or_equal:holidays.*.startDate', + 'holidays.*.type' => 'required|in:public,company,alternative,temporary', + 'holidays.*.isRecurring' => 'boolean', + ]); + + $tenantId = session('selected_tenant_id', 1); + $count = 0; + + foreach ($request->input('holidays') as $item) { + Holiday::create([ + 'tenant_id' => $tenantId, + 'start_date' => $item['startDate'], + 'end_date' => $item['endDate'], + 'name' => $item['name'], + 'type' => $item['type'] ?? 'public', + 'is_recurring' => $item['isRecurring'] ?? false, + 'memo' => $item['memo'] ?? null, + ]); + $count++; + } + + return response()->json([ + 'success' => true, + 'message' => "{$count}건의 휴일이 등록되었습니다.", + ]); + } +} diff --git a/app/Models/System/Holiday.php b/app/Models/System/Holiday.php new file mode 100644 index 00000000..d9a72252 --- /dev/null +++ b/app/Models/System/Holiday.php @@ -0,0 +1,41 @@ + 'date', + 'end_date' => 'date', + 'is_recurring' => 'boolean', + ]; + + public function scopeForTenant($query, int $tenantId) + { + return $query->where('tenant_id', $tenantId); + } + + public function scopeForYear($query, int $year) + { + return $query->whereYear('start_date', $year); + } +} diff --git a/database/seeders/HolidayMenuSeeder.php b/database/seeders/HolidayMenuSeeder.php new file mode 100644 index 00000000..0b26fac8 --- /dev/null +++ b/database/seeders/HolidayMenuSeeder.php @@ -0,0 +1,63 @@ +where('name', '시스템 관리') + ->first(); + + if (!$parentMenu) { + $this->command->error('시스템 관리 메뉴를 찾을 수 없습니다.'); + return; + } + + // 이미 존재하는지 확인 + $existingMenu = Menu::where('tenant_id', $tenantId) + ->where('name', '달력 휴일 관리') + ->where('parent_id', $parentMenu->id) + ->first(); + + if ($existingMenu) { + $this->command->info('달력 휴일 관리 메뉴가 이미 존재합니다.'); + return; + } + + // 현재 자식 메뉴 최대 sort_order 확인 + $maxSort = Menu::where('parent_id', $parentMenu->id) + ->max('sort_order') ?? 0; + + // 메뉴 생성 + $menu = Menu::create([ + 'tenant_id' => $tenantId, + 'parent_id' => $parentMenu->id, + 'name' => '달력 휴일 관리', + 'url' => '/system/holidays', + 'icon' => 'calendar', + 'sort_order' => $maxSort + 1, + 'is_active' => true, + ]); + + $this->command->info("메뉴 생성 완료: {$menu->name} (sort_order: {$menu->sort_order})"); + + // 하위 메뉴 목록 출력 + $this->command->info(''); + $this->command->info('=== 시스템 관리 하위 메뉴 ==='); + $children = Menu::where('parent_id', $parentMenu->id) + ->orderBy('sort_order') + ->get(['name', 'url', 'sort_order']); + + foreach ($children as $child) { + $this->command->info("{$child->sort_order}. {$child->name} ({$child->url})"); + } + } +} diff --git a/resources/views/system/holidays/index.blade.php b/resources/views/system/holidays/index.blade.php new file mode 100644 index 00000000..8d7f80dd --- /dev/null +++ b/resources/views/system/holidays/index.blade.php @@ -0,0 +1,538 @@ +@extends('layouts.app') + +@section('title', '달력 휴일 관리') + +@push('styles') + +@endpush + +@section('content') + +
+@endsection + +@push('scripts') + + + + +@verbatim + +@endverbatim +@endpush diff --git a/routes/web.php b/routes/web.php index a3812a9e..de79167b 100644 --- a/routes/web.php +++ b/routes/web.php @@ -34,6 +34,7 @@ use App\Http\Controllers\RolePermissionController; use App\Http\Controllers\Sales\SalesProductController; use App\Http\Controllers\System\AiConfigController; +use App\Http\Controllers\System\HolidayController; use App\Http\Controllers\Stats\StatDashboardController; use App\Http\Controllers\System\SystemAlertController; use App\Http\Controllers\TenantController; @@ -392,6 +393,16 @@ Route::post('/test-gcs', [AiConfigController::class, 'testGcs'])->name('test-gcs'); }); + // 달력 휴일 관리 + Route::prefix('system/holidays')->name('system.holidays.')->group(function () { + Route::get('/', [HolidayController::class, 'index'])->name('index'); + Route::get('/list', [HolidayController::class, 'list'])->name('list'); + Route::post('/', [HolidayController::class, 'store'])->name('store'); + Route::put('/{id}', [HolidayController::class, 'update'])->name('update'); + Route::delete('/{id}', [HolidayController::class, 'destroy'])->name('destroy'); + Route::post('/bulk', [HolidayController::class, 'bulkStore'])->name('bulk'); + }); + // 명함 OCR API Route::post('/api/business-card-ocr', [BusinessCardOcrController::class, 'process'])->name('api.business-card-ocr');