fundScheduleService->getSchedules( $request->all(), $request->integer('per_page', 15) ); // HTMX 요청인 경우 HTML 반환 if ($request->header('HX-Request')) { return response(view('finance.fund-schedules.partials.table', compact('schedules'))); } return response()->json([ 'success' => true, 'data' => $schedules->items(), 'meta' => [ 'current_page' => $schedules->currentPage(), 'last_page' => $schedules->lastPage(), 'per_page' => $schedules->perPage(), 'total' => $schedules->total(), ], ]); } /** * 캘린더용 월별 일정 조회 */ public function calendar(Request $request): JsonResponse|Response { $year = $request->integer('year', now()->year); $month = $request->integer('month', now()->month); $calendarData = $this->fundScheduleService->getCalendarData($year, $month); $summary = $this->fundScheduleService->getMonthlySummary($year, $month); // HTMX 요청인 경우 캘린더 HTML 반환 if ($request->header('HX-Request')) { return response(view('finance.fund-schedules.partials.calendar', compact('year', 'month', 'calendarData', 'summary'))); } return response()->json([ 'success' => true, 'data' => [ 'year' => $year, 'month' => $month, 'schedules' => $calendarData, 'summary' => $summary, ], ]); } /** * 일정 상세 조회 */ public function show(int $id): JsonResponse { $schedule = $this->fundScheduleService->getScheduleById($id); if (! $schedule) { return response()->json([ 'success' => false, 'message' => '일정을 찾을 수 없습니다.', ], 404); } return response()->json([ 'success' => true, 'data' => $schedule, ]); } /** * 일정 생성 */ public function store(Request $request): JsonResponse { $validated = $request->validate([ 'title' => 'required|string|max:200', 'description' => 'nullable|string', 'schedule_type' => 'required|in:income,expense', 'scheduled_date' => 'required|date', 'amount' => 'required|numeric|min:0', 'currency' => 'nullable|string|max:3', 'related_bank_account_id' => 'nullable|integer|exists:bank_accounts,id', 'counterparty' => 'nullable|string|max:200', 'category' => 'nullable|string|max:50', 'status' => 'nullable|in:pending,completed,cancelled', 'is_recurring' => 'nullable|boolean', 'recurrence_rule' => 'nullable|in:daily,weekly,monthly,yearly', 'recurrence_end_date' => 'nullable|date|after:scheduled_date', 'memo' => 'nullable|string', ]); $schedule = $this->fundScheduleService->createSchedule($validated); return response()->json([ 'success' => true, 'message' => '일정이 등록되었습니다.', 'data' => $schedule, ], 201); } /** * 일정 수정 */ public function update(Request $request, int $id): JsonResponse { $schedule = $this->fundScheduleService->getScheduleById($id); if (! $schedule) { return response()->json([ 'success' => false, 'message' => '일정을 찾을 수 없습니다.', ], 404); } $validated = $request->validate([ 'title' => 'sometimes|required|string|max:200', 'description' => 'nullable|string', 'schedule_type' => 'sometimes|required|in:income,expense', 'scheduled_date' => 'sometimes|required|date', 'amount' => 'sometimes|required|numeric|min:0', 'currency' => 'nullable|string|max:3', 'related_bank_account_id' => 'nullable|integer|exists:bank_accounts,id', 'counterparty' => 'nullable|string|max:200', 'category' => 'nullable|string|max:50', 'status' => 'nullable|in:pending,completed,cancelled', 'is_recurring' => 'nullable|boolean', 'recurrence_rule' => 'nullable|in:daily,weekly,monthly,yearly', 'recurrence_end_date' => 'nullable|date', 'memo' => 'nullable|string', ]); $schedule = $this->fundScheduleService->updateSchedule($schedule, $validated); return response()->json([ 'success' => true, 'message' => '일정이 수정되었습니다.', 'data' => $schedule, ]); } /** * 일정 삭제 (Soft Delete) */ public function destroy(Request $request, int $id): JsonResponse|Response { $schedule = $this->fundScheduleService->getScheduleById($id); if (! $schedule) { return response()->json([ 'success' => false, 'message' => '일정을 찾을 수 없습니다.', ], 404); } $this->fundScheduleService->deleteSchedule($schedule); // HTMX 요청인 경우 갱신된 캘린더 반환 if ($request->header('HX-Request')) { $year = $request->integer('year', now()->year); $month = $request->integer('month', now()->month); $calendarData = $this->fundScheduleService->getCalendarData($year, $month); $summary = $this->fundScheduleService->getMonthlySummary($year, $month); return response(view('finance.fund-schedules.partials.calendar', compact('year', 'month', 'calendarData', 'summary'))); } return response()->json([ 'success' => true, 'message' => '일정이 삭제되었습니다.', ]); } /** * 상태 변경 */ public function updateStatus(Request $request, int $id): JsonResponse|Response { $schedule = $this->fundScheduleService->getScheduleById($id); if (! $schedule) { return response()->json([ 'success' => false, 'message' => '일정을 찾을 수 없습니다.', ], 404); } $validated = $request->validate([ 'status' => 'required|in:pending,completed,cancelled', 'completed_amount' => 'nullable|numeric|min:0', 'completed_date' => 'nullable|date', ]); if ($validated['status'] === FundSchedule::STATUS_COMPLETED) { $schedule = $this->fundScheduleService->markAsCompleted( $schedule, $validated['completed_amount'] ?? null, $validated['completed_date'] ?? null ); } elseif ($validated['status'] === FundSchedule::STATUS_CANCELLED) { $schedule = $this->fundScheduleService->markAsCancelled($schedule); } else { $schedule = $this->fundScheduleService->updateStatus($schedule, $validated['status']); } // HTMX 요청인 경우 갱신된 캘린더 반환 if ($request->header('HX-Request')) { $year = $request->integer('year', now()->year); $month = $request->integer('month', now()->month); $calendarData = $this->fundScheduleService->getCalendarData($year, $month); $summary = $this->fundScheduleService->getMonthlySummary($year, $month); return response(view('finance.fund-schedules.partials.calendar', compact('year', 'month', 'calendarData', 'summary'))); } return response()->json([ 'success' => true, 'message' => '상태가 변경되었습니다.', 'data' => $schedule, ]); } /** * 월별 일정 복사 */ public function copy(Request $request): JsonResponse { $validated = $request->validate([ 'source_year' => 'required|integer|min:2000|max:2100', 'source_month' => 'required|integer|min:1|max:12', 'target_year' => 'required|integer|min:2000|max:2100', 'target_month' => 'required|integer|min:1|max:12', ]); if ($validated['source_year'] === $validated['target_year'] && $validated['source_month'] === $validated['target_month']) { return response()->json([ 'success' => false, 'message' => '원본 월과 대상 월이 동일합니다.', ], 422); } $copiedCount = $this->fundScheduleService->copySchedulesToMonth( $validated['source_year'], $validated['source_month'], $validated['target_year'], $validated['target_month'] ); if ($copiedCount === 0) { return response()->json([ 'success' => false, 'message' => '복사할 일정이 없습니다.', ], 422); } return response()->json([ 'success' => true, 'message' => "{$copiedCount}건의 일정이 복사되었습니다.", 'data' => ['copied_count' => $copiedCount], ]); } /** * 월별 요약 통계 */ public function summary(Request $request): JsonResponse { $year = $request->integer('year', now()->year); $month = $request->integer('month', now()->month); $summary = $this->fundScheduleService->getMonthlySummary($year, $month); return response()->json([ 'success' => true, 'data' => $summary, ]); } /** * 예정 일정 조회 */ public function upcoming(Request $request): JsonResponse { $days = $request->integer('days', 30); $schedules = $this->fundScheduleService->getUpcomingSchedules($days); return response()->json([ 'success' => true, 'data' => $schedules, ]); } }