Files
sam-api/app/Services/ReportService.php
hskwon 77914da7b7 feat: 보고서(Reports) API 구현
- 일일 일보 조회/엑셀 다운로드 API 추가
- 지출 예상 내역서 조회/엑셀 다운로드 API 추가
- ReportService: 전일/당일 잔액 계산, 월별 지출 예상 집계
- Laravel Excel을 이용한 엑셀 내보내기 구현
- Swagger 문서 작성 완료
2025-12-17 22:51:17 +09:00

223 lines
7.6 KiB
PHP

<?php
namespace App\Services;
use App\Models\Tenants\Deposit;
use App\Models\Tenants\Purchase;
use App\Models\Tenants\Withdrawal;
use Carbon\Carbon;
class ReportService extends Service
{
/**
* 일일 일보 조회
*
* @param array $params [date: 기준일]
*/
public function dailyReport(array $params): array
{
$tenantId = $this->tenantId();
$date = Carbon::parse($params['date'] ?? now()->toDateString());
$previousDate = $date->copy()->subDay();
// 전일 잔액 계산 (기준일 전일까지의 모든 입출금 합계)
$previousBalance = $this->calculateBalanceUntilDate($tenantId, $previousDate);
// 당일 입금 합계
$dailyDeposit = Deposit::query()
->where('tenant_id', $tenantId)
->whereDate('deposit_date', $date)
->sum('amount');
// 당일 출금 합계
$dailyWithdrawal = Withdrawal::query()
->where('tenant_id', $tenantId)
->whereDate('withdrawal_date', $date)
->sum('amount');
// 당일 잔액
$currentBalance = $previousBalance + $dailyDeposit - $dailyWithdrawal;
// 상세 내역 (입출금 통합)
$details = $this->getDailyDetails($tenantId, $date);
return [
'date' => $date->toDateString(),
'previous_balance' => (float) $previousBalance,
'daily_deposit' => (float) $dailyDeposit,
'daily_withdrawal' => (float) $dailyWithdrawal,
'current_balance' => (float) $currentBalance,
'details' => $details,
];
}
/**
* 지출 예상 내역서 조회
*
* @param array $params [year_month: YYYY-MM 형식]
*/
public function expenseEstimate(array $params): array
{
$tenantId = $this->tenantId();
$yearMonth = $params['year_month'] ?? now()->format('Y-m');
[$year, $month] = explode('-', $yearMonth);
$startDate = Carbon::createFromDate($year, $month, 1)->startOfMonth();
$endDate = $startDate->copy()->endOfMonth();
// 미결제 매입 내역 조회 (지출 예상)
$items = Purchase::query()
->where('tenant_id', $tenantId)
->whereIn('status', ['draft', 'confirmed'])
->whereNull('withdrawal_id')
->whereBetween('purchase_date', [$startDate, $endDate])
->with(['client:id,name'])
->orderBy('purchase_date')
->get()
->map(function ($purchase) {
return [
'id' => $purchase->id,
'expected_date' => $purchase->purchase_date->toDateString(),
'item_name' => $purchase->description ?? __('message.report.purchase'),
'amount' => (float) $purchase->total_amount,
'client_name' => $purchase->client?->name ?? '',
'account_name' => '',
];
});
// 예상 지출 합계
$totalEstimate = $items->sum('amount');
// 계좌 잔액 (대표 계좌 기준, 없으면 모든 계좌 합산)
$accountBalance = $this->getAccountBalance($tenantId);
// 예상 잔액
$expectedBalance = $accountBalance - $totalEstimate;
// 월별 합계
$monthlySummary = $this->getMonthlySummary($tenantId, $startDate);
return [
'year_month' => $yearMonth,
'total_estimate' => (float) $totalEstimate,
'account_balance' => (float) $accountBalance,
'expected_balance' => (float) $expectedBalance,
'items' => $items->values()->toArray(),
'monthly_summary' => $monthlySummary,
];
}
/**
* 특정 날짜까지의 잔액 계산
*/
private function calculateBalanceUntilDate(int $tenantId, Carbon $date): float
{
$totalDeposits = Deposit::query()
->where('tenant_id', $tenantId)
->whereDate('deposit_date', '<=', $date)
->sum('amount');
$totalWithdrawals = Withdrawal::query()
->where('tenant_id', $tenantId)
->whereDate('withdrawal_date', '<=', $date)
->sum('amount');
return (float) ($totalDeposits - $totalWithdrawals);
}
/**
* 일일 상세 내역 조회 (입출금 통합)
*/
private function getDailyDetails(int $tenantId, Carbon $date): array
{
// 입금 내역
$deposits = Deposit::query()
->where('tenant_id', $tenantId)
->whereDate('deposit_date', $date)
->with(['client:id,name'])
->get()
->map(function ($deposit) {
return [
'type' => 'deposit',
'type_label' => __('message.report.deposit'),
'client_name' => $deposit->client?->name ?? $deposit->client_name ?? '',
'account_code' => $deposit->account_code ?? '',
'deposit_amount' => (float) $deposit->amount,
'withdrawal_amount' => 0,
'description' => $deposit->description ?? '',
'payment_method' => $deposit->payment_method,
];
});
// 출금 내역
$withdrawals = Withdrawal::query()
->where('tenant_id', $tenantId)
->whereDate('withdrawal_date', $date)
->with(['client:id,name'])
->get()
->map(function ($withdrawal) {
return [
'type' => 'withdrawal',
'type_label' => __('message.report.withdrawal'),
'client_name' => $withdrawal->client?->name ?? $withdrawal->client_name ?? '',
'account_code' => $withdrawal->account_code ?? '',
'deposit_amount' => 0,
'withdrawal_amount' => (float) $withdrawal->amount,
'description' => $withdrawal->description ?? '',
'payment_method' => $withdrawal->payment_method,
];
});
return $deposits->concat($withdrawals)->values()->toArray();
}
/**
* 계좌 잔액 조회
* 입출금 내역 기반으로 계산
*/
private function getAccountBalance(int $tenantId): float
{
return $this->calculateBalanceUntilDate($tenantId, Carbon::now());
}
/**
* 월별 지출 예상 합계
*/
private function getMonthlySummary(int $tenantId, Carbon $baseDate): array
{
$summary = [];
// 기준월부터 3개월간 집계
for ($i = 0; $i < 3; $i++) {
$targetDate = $baseDate->copy()->addMonths($i);
$startOfMonth = $targetDate->copy()->startOfMonth();
$endOfMonth = $targetDate->copy()->endOfMonth();
$total = Purchase::query()
->where('tenant_id', $tenantId)
->whereIn('status', ['draft', 'confirmed'])
->whereNull('withdrawal_id')
->whereBetween('purchase_date', [$startOfMonth, $endOfMonth])
->sum('total_amount');
$summary[] = [
'month' => $targetDate->format('Y/m'),
'total' => (float) $total,
];
}
// 전체 합계
$grandTotal = collect($summary)->sum('total');
// 계좌 잔액
$accountBalance = $this->getAccountBalance($tenantId);
return [
'by_month' => $summary,
'total_expense' => $grandTotal,
'account_balance' => $accountBalance,
'final_difference' => $accountBalance - $grandTotal,
];
}
}