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'; // 빨강 - 자금 부족 우려 } }