Files
sam-api/app/Services/DailyReportService.php
권혁성 d186a0c111 오늘 이슈(TodayIssue) 기능 구현
- TodayIssue 모델 및 마이그레이션 추가
- TodayIssueController, TodayIssueService 구현
- TodayIssueObserverService 및 Observer 패턴 적용
- DailyReportService 연동
- Swagger API 문서 업데이트
- 라우트 추가
2026-01-22 09:47:29 +09:00

223 lines
8.1 KiB
PHP

<?php
namespace App\Services;
use App\Models\Tenants\BankAccount;
use App\Models\Tenants\Bill;
use App\Models\Tenants\Deposit;
use App\Models\Tenants\Withdrawal;
use Carbon\Carbon;
/**
* 일일 보고서 서비스
*/
class DailyReportService extends Service
{
/**
* 어음 및 외상매출채권 현황 조회
*/
public function noteReceivables(array $params): array
{
$tenantId = $this->tenantId();
$date = isset($params['date']) ? Carbon::parse($params['date']) : Carbon::today();
// 수취어음 중 보관중 상태인 것만 조회 (만기일 기준)
$bills = Bill::where('tenant_id', $tenantId)
->where('bill_type', 'received')
->where('status', 'stored')
->where('maturity_date', '>=', $date->copy()->startOfDay())
->orderBy('maturity_date', 'asc')
->get();
return $bills->map(function ($bill) {
return [
'id' => (string) $bill->id,
'content' => "(수취어음) {$bill->display_client_name} - {$bill->bill_number}",
'current_balance' => (float) $bill->amount,
'issue_date' => $bill->issue_date?->format('Y-m-d'),
'due_date' => $bill->maturity_date?->format('Y-m-d'),
];
})->values()->toArray();
}
/**
* 일별 계좌 현황 조회
*/
public function dailyAccounts(array $params): array
{
$tenantId = $this->tenantId();
$date = isset($params['date']) ? Carbon::parse($params['date']) : Carbon::today();
$startOfMonth = $date->copy()->startOfMonth();
$endOfDay = $date->copy()->endOfDay();
// 활성 계좌 목록
$accounts = BankAccount::where('tenant_id', $tenantId)
->where('status', 'active')
->orderBy('is_primary', 'desc')
->orderBy('bank_name', 'asc')
->get();
$result = [];
foreach ($accounts as $account) {
// 전월 이월: 이번 달 1일 이전까지의 누적 잔액
$carryoverDeposits = Deposit::where('tenant_id', $tenantId)
->where('bank_account_id', $account->id)
->where('deposit_date', '<', $startOfMonth)
->sum('amount');
$carryoverWithdrawals = Withdrawal::where('tenant_id', $tenantId)
->where('bank_account_id', $account->id)
->where('withdrawal_date', '<', $startOfMonth)
->sum('amount');
$carryover = $carryoverDeposits - $carryoverWithdrawals;
// 당일 수입 (입금)
$income = Deposit::where('tenant_id', $tenantId)
->where('bank_account_id', $account->id)
->whereBetween('deposit_date', [$startOfMonth, $endOfDay])
->sum('amount');
// 당일 지출 (출금)
$expense = Withdrawal::where('tenant_id', $tenantId)
->where('bank_account_id', $account->id)
->whereBetween('withdrawal_date', [$startOfMonth, $endOfDay])
->sum('amount');
// 잔액 = 전월이월 + 수입 - 지출
$balance = $carryover + $income - $expense;
// 매칭 상태: Deposit과 Withdrawal 금액이 일치하면 matched
$matchStatus = abs($income - $expense) < 0.01 ? 'matched' : 'unmatched';
$result[] = [
'id' => (string) $account->id,
'category' => "{$account->bank_name} {$account->getMaskedAccountNumber()}",
'match_status' => $matchStatus,
'carryover' => (float) $carryover,
'income' => (float) $income,
'expense' => (float) $expense,
'balance' => (float) $balance,
'currency' => 'KRW', // 현재는 KRW만 지원
];
}
return $result;
}
/**
* 일일 보고서 요약 통계
*/
public function summary(array $params): array
{
$tenantId = $this->tenantId();
$date = isset($params['date']) ? Carbon::parse($params['date']) : Carbon::today();
// 어음 합계
$noteReceivableTotal = Bill::where('tenant_id', $tenantId)
->where('bill_type', 'received')
->where('status', 'stored')
->where('maturity_date', '>=', $date->copy()->startOfDay())
->sum('amount');
// 계좌별 현황
$dailyAccounts = $this->dailyAccounts($params);
// 통화별 합계
$krwTotal = collect($dailyAccounts)
->where('currency', 'KRW')
->reduce(function ($carry, $item) {
return [
'carryover' => $carry['carryover'] + $item['carryover'],
'income' => $carry['income'] + $item['income'],
'expense' => $carry['expense'] + $item['expense'],
'balance' => $carry['balance'] + $item['balance'],
];
}, ['carryover' => 0, 'income' => 0, 'expense' => 0, 'balance' => 0]);
$usdTotal = collect($dailyAccounts)
->where('currency', 'USD')
->reduce(function ($carry, $item) {
return [
'carryover' => $carry['carryover'] + $item['carryover'],
'income' => $carry['income'] + $item['income'],
'expense' => $carry['expense'] + $item['expense'],
'balance' => $carry['balance'] + $item['balance'],
];
}, ['carryover' => 0, 'income' => 0, 'expense' => 0, 'balance' => 0]);
// 운영자금 안정성 계산
$cashAssetTotal = (float) $krwTotal['balance'];
$monthlyOperatingExpense = $this->calculateMonthlyOperatingExpense($date);
$operatingMonths = $monthlyOperatingExpense > 0
? round($cashAssetTotal / $monthlyOperatingExpense, 1)
: null;
$operatingStability = $this->getOperatingStability($operatingMonths);
return [
'date' => $date->format('Y-m-d'),
'day_of_week' => $date->locale('ko')->dayName,
'note_receivable_total' => (float) $noteReceivableTotal,
'foreign_currency_total' => (float) $usdTotal['balance'],
'cash_asset_total' => $cashAssetTotal,
'krw_totals' => $krwTotal,
'usd_totals' => $usdTotal,
// 운영자금 안정성 지표
'monthly_operating_expense' => $monthlyOperatingExpense,
'operating_months' => $operatingMonths,
'operating_stability' => $operatingStability,
];
}
/**
* 직전 3개월 평균 월 운영비 계산
*
* @param Carbon $baseDate 기준일
* @return float 월 평균 운영비
*/
private function calculateMonthlyOperatingExpense(Carbon $baseDate): float
{
$tenantId = $this->tenantId();
// 직전 3개월 범위: 기준일 전월 말일부터 3개월 전 1일까지
$lastMonthEnd = $baseDate->copy()->subMonth()->endOfMonth();
$threeMonthsAgo = $baseDate->copy()->subMonths(3)->startOfMonth();
$totalExpense = Withdrawal::where('tenant_id', $tenantId)
->whereBetween('withdrawal_date', [$threeMonthsAgo, $lastMonthEnd])
->sum('amount');
// 3개월 평균
return round((float) $totalExpense / 3, 0);
}
/**
* 운영자금 안정성 판정
*
* 색상 가이드 기준:
* - warning (빨강): 3개월 미만 - 자금 부족 우려
* - caution (주황): 3~6개월 - 자금 관리 필요
* - stable (파랑): 6개월 이상 - 안정적
*
* @param float|null $months 운영 가능 개월 수
* @return string 안정성 상태 (stable|caution|warning|unknown)
*/
private function getOperatingStability(?float $months): string
{
if ($months === null) {
return 'unknown';
}
if ($months >= 6) {
return 'stable'; // 파랑 - 안정적
}
if ($months >= 3) {
return 'caution'; // 주황 - 자금 관리 필요
}
return 'warning'; // 빨강 - 자금 부족 우려
}
}