diff --git a/app/Http/Controllers/Api/Admin/FundScheduleController.php b/app/Http/Controllers/Api/Admin/FundScheduleController.php
index a752f957..e962146f 100644
--- a/app/Http/Controllers/Api/Admin/FundScheduleController.php
+++ b/app/Http/Controllers/Api/Admin/FundScheduleController.php
@@ -239,6 +239,47 @@ public function updateStatus(Request $request, int $id): JsonResponse|Response
]);
}
+ /**
+ * 월별 일정 복사
+ */
+ 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],
+ ]);
+ }
+
/**
* 월별 요약 통계
*/
diff --git a/app/Services/FundScheduleService.php b/app/Services/FundScheduleService.php
index c2da0be6..132c60f8 100644
--- a/app/Services/FundScheduleService.php
+++ b/app/Services/FundScheduleService.php
@@ -218,6 +218,53 @@ public function updateStatus(FundSchedule $schedule, string $status): FundSchedu
return $schedule->fresh();
}
+ // =========================================================================
+ // 월별 복사
+ // =========================================================================
+
+ /**
+ * 원본 월의 일정을 대상 월로 복사
+ */
+ public function copySchedulesToMonth(int $sourceYear, int $sourceMonth, int $targetYear, int $targetMonth): int
+ {
+ $schedules = $this->getSchedulesForMonth($sourceYear, $sourceMonth);
+
+ if ($schedules->isEmpty()) {
+ return 0;
+ }
+
+ $targetLastDay = cal_days_in_month(CAL_GREGORIAN, $targetMonth, $targetYear);
+ $copiedCount = 0;
+
+ foreach ($schedules as $schedule) {
+ $sourceDay = $schedule->scheduled_date->day;
+ $targetDay = min($sourceDay, $targetLastDay);
+
+ $targetDate = sprintf('%04d-%02d-%02d', $targetYear, $targetMonth, $targetDay);
+
+ $this->createSchedule([
+ 'title' => $schedule->title,
+ 'description' => $schedule->description,
+ 'schedule_type' => $schedule->schedule_type,
+ 'scheduled_date' => $targetDate,
+ 'amount' => $schedule->amount,
+ 'currency' => $schedule->currency,
+ 'related_bank_account_id' => $schedule->related_bank_account_id,
+ 'counterparty' => $schedule->counterparty,
+ 'category' => $schedule->category,
+ 'status' => FundSchedule::STATUS_PENDING,
+ 'is_recurring' => $schedule->is_recurring,
+ 'recurrence_rule' => $schedule->recurrence_rule,
+ 'recurrence_end_date' => $schedule->recurrence_end_date,
+ 'memo' => $schedule->memo,
+ ]);
+
+ $copiedCount++;
+ }
+
+ return $copiedCount;
+ }
+
// =========================================================================
// 일괄 작업
// =========================================================================
diff --git a/resources/views/finance/fund-schedules/index.blade.php b/resources/views/finance/fund-schedules/index.blade.php
index 3029f30f..9de41eb9 100644
--- a/resources/views/finance/fund-schedules/index.blade.php
+++ b/resources/views/finance/fund-schedules/index.blade.php
@@ -122,6 +122,15 @@ class="p-2 hover:bg-gray-100 rounded-lg transition-colors">