Files
sam-manage/app/Services/FundScheduleService.php

362 lines
11 KiB
PHP
Raw Normal View History

2026-01-20 20:21:06 +09:00
<?php
namespace App\Services;
use App\Models\Finance\FundSchedule;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
class FundScheduleService
{
// =========================================================================
// 일정 목록 조회
// =========================================================================
/**
* 일정 목록 조회 (페이지네이션)
*/
public function getSchedules(array $filters = [], int $perPage = 15): LengthAwarePaginator
{
$query = FundSchedule::query();
// Soft Delete 필터
if (isset($filters['trashed'])) {
if ($filters['trashed'] === 'only') {
$query->onlyTrashed();
} elseif ($filters['trashed'] === 'with') {
$query->withTrashed();
}
}
// 검색 필터
if (! empty($filters['search'])) {
$search = $filters['search'];
$query->where(function ($q) use ($search) {
$q->where('title', 'like', "%{$search}%")
->orWhere('counterparty', 'like', "%{$search}%")
->orWhere('description', 'like', "%{$search}%");
});
}
// 일정 유형 필터
if (! empty($filters['schedule_type'])) {
$query->where('schedule_type', $filters['schedule_type']);
}
// 상태 필터
if (! empty($filters['status'])) {
$query->where('status', $filters['status']);
}
// 날짜 범위 필터
if (! empty($filters['start_date']) && ! empty($filters['end_date'])) {
$query->dateBetween($filters['start_date'], $filters['end_date']);
} elseif (! empty($filters['start_date'])) {
$query->where('scheduled_date', '>=', $filters['start_date']);
} elseif (! empty($filters['end_date'])) {
$query->where('scheduled_date', '<=', $filters['end_date']);
}
// 월별 필터
if (! empty($filters['year']) && ! empty($filters['month'])) {
$query->forMonth((int) $filters['year'], (int) $filters['month']);
}
// 카테고리 필터
if (! empty($filters['category'])) {
$query->where('category', $filters['category']);
}
return $query
->with('bankAccount:id,bank_name,account_number')
->orderBy('scheduled_date')
->orderBy('id')
->paginate($perPage);
}
/**
* 월별 일정 조회 (캘린더용)
*/
public function getSchedulesForMonth(int $year, int $month): Collection
{
return FundSchedule::forMonth($year, $month)
->with('bankAccount:id,bank_name,account_number')
->ordered()
->get();
}
/**
* 특정 날짜의 일정 조회
*/
public function getSchedulesForDate(string $date): Collection
{
return FundSchedule::where('scheduled_date', $date)
->with('bankAccount:id,bank_name,account_number')
->ordered()
->get();
}
/**
* 예정된 일정 조회 (향후 N일)
*/
public function getUpcomingSchedules(int $days = 30): Collection
{
$startDate = now()->toDateString();
$endDate = now()->addDays($days)->toDateString();
return FundSchedule::pending()
->dateBetween($startDate, $endDate)
->with('bankAccount:id,bank_name,account_number')
->ordered()
->get();
}
// =========================================================================
// 일정 CRUD
// =========================================================================
/**
* 일정 상세 조회
*/
public function getScheduleById(int $id, bool $withTrashed = false): ?FundSchedule
{
$query = FundSchedule::query();
if ($withTrashed) {
$query->withTrashed();
}
return $query->with('bankAccount')->find($id);
}
/**
* 일정 생성
*/
public function createSchedule(array $data): FundSchedule
{
$data['created_by'] = auth()->id();
$data['tenant_id'] = $data['tenant_id'] ?? session('selected_tenant_id') ?? auth()->user()?->tenant_id;
return FundSchedule::create($data);
}
/**
* 일정 수정
*/
public function updateSchedule(FundSchedule $schedule, array $data): FundSchedule
{
$data['updated_by'] = auth()->id();
$schedule->update($data);
return $schedule->fresh();
}
/**
* 일정 삭제 (Soft Delete)
*/
public function deleteSchedule(FundSchedule $schedule): bool
{
$schedule->deleted_by = auth()->id();
$schedule->save();
return $schedule->delete();
}
/**
* 일정 복원
*/
public function restoreSchedule(FundSchedule $schedule): bool
{
$schedule->deleted_by = null;
return $schedule->restore();
}
/**
* 일정 영구 삭제
*/
public function forceDeleteSchedule(FundSchedule $schedule): bool
{
return $schedule->forceDelete();
}
// =========================================================================
// 상태 변경
// =========================================================================
/**
* 완료 처리
*/
public function markAsCompleted(FundSchedule $schedule, ?float $actualAmount = null, ?string $completedDate = null): FundSchedule
{
$schedule->markAsCompleted($actualAmount, $completedDate);
return $schedule->fresh();
}
/**
* 취소 처리
*/
public function markAsCancelled(FundSchedule $schedule): FundSchedule
{
$schedule->markAsCancelled();
return $schedule->fresh();
}
/**
* 상태 변경
*/
public function updateStatus(FundSchedule $schedule, string $status): FundSchedule
{
$schedule->update([
'status' => $status,
'updated_by' => auth()->id(),
]);
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;
}
2026-01-20 20:21:06 +09:00
// =========================================================================
// 일괄 작업
// =========================================================================
/**
* 일괄 삭제
*/
public function bulkDelete(array $ids): int
{
return FundSchedule::whereIn('id', $ids)
->update([
'deleted_by' => auth()->id(),
'deleted_at' => now(),
]);
}
/**
* 일괄 상태 변경
*/
public function bulkUpdateStatus(array $ids, string $status): int
{
return FundSchedule::whereIn('id', $ids)
->update([
'status' => $status,
'updated_by' => auth()->id(),
]);
}
// =========================================================================
// 요약 및 통계
// =========================================================================
/**
* 월별 요약 통계
*/
public function getMonthlySummary(int $year, int $month): array
{
$schedules = FundSchedule::forMonth($year, $month)->get();
$incomeSchedules = $schedules->where('schedule_type', FundSchedule::TYPE_INCOME);
$expenseSchedules = $schedules->where('schedule_type', FundSchedule::TYPE_EXPENSE);
return [
'year' => $year,
'month' => $month,
'total_count' => $schedules->count(),
'income' => [
'count' => $incomeSchedules->count(),
'total' => $incomeSchedules->sum('amount'),
'pending' => $incomeSchedules->where('status', FundSchedule::STATUS_PENDING)->sum('amount'),
2026-02-25 11:45:01 +09:00
'completed' => $incomeSchedules->where('status', FundSchedule::STATUS_COMPLETED)->sum(fn ($s) => $s->completed_amount ?: $s->amount),
2026-01-20 20:21:06 +09:00
],
'expense' => [
'count' => $expenseSchedules->count(),
'total' => $expenseSchedules->sum('amount'),
'pending' => $expenseSchedules->where('status', FundSchedule::STATUS_PENDING)->sum('amount'),
2026-02-25 11:45:01 +09:00
'completed' => $expenseSchedules->where('status', FundSchedule::STATUS_COMPLETED)->sum(fn ($s) => $s->completed_amount ?: $s->amount),
2026-01-20 20:21:06 +09:00
],
'net' => $incomeSchedules->sum('amount') - $expenseSchedules->sum('amount'),
];
}
/**
* 캘린더용 데이터 구조화
*/
public function getCalendarData(int $year, int $month): array
{
$schedules = $this->getSchedulesForMonth($year, $month);
// 날짜별로 그룹화
$groupedByDate = $schedules->groupBy(function ($schedule) {
return $schedule->scheduled_date->format('Y-m-d');
});
return $groupedByDate->toArray();
}
/**
* 전체 요약 통계
*/
public function getSummary(): array
{
$pending = FundSchedule::pending()->get();
return [
'pending_count' => $pending->count(),
'pending_income' => $pending->where('schedule_type', FundSchedule::TYPE_INCOME)->sum('amount'),
'pending_expense' => $pending->where('schedule_type', FundSchedule::TYPE_EXPENSE)->sum('amount'),
'upcoming_7days' => FundSchedule::pending()
->dateBetween(now()->toDateString(), now()->addDays(7)->toDateString())
->count(),
];
}
}