- 영업(Sales), 재무(Finance), 생산(Production) 3개 도메인 구현 - 일간/월간 통계 테이블 6개 마이그레이션 생성 - 도메인별 StatService (SalesStatService, FinanceStatService, ProductionStatService) - Daily/Monthly 6개 Eloquent 모델 생성 - StatAggregatorService에 도메인 서비스 매핑 활성화 - StatJobLog duration_ms abs() 처리 - 스케줄러 등록 (일간 02:00, 월간 1일 03:00) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
173 lines
7.0 KiB
PHP
173 lines
7.0 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Stats;
|
|
|
|
use App\Models\Stats\Daily\StatFinanceDaily;
|
|
use App\Models\Stats\Monthly\StatFinanceMonthly;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class FinanceStatService implements StatDomainServiceInterface
|
|
{
|
|
public function aggregateDaily(int $tenantId, Carbon $date): int
|
|
{
|
|
$dateStr = $date->format('Y-m-d');
|
|
|
|
// 입금 (deposits)
|
|
$depositStats = DB::connection('mysql')
|
|
->table('deposits')
|
|
->where('tenant_id', $tenantId)
|
|
->where('deposit_date', $dateStr)
|
|
->whereNull('deleted_at')
|
|
->selectRaw('COUNT(*) as cnt, COALESCE(SUM(amount), 0) as total')
|
|
->first();
|
|
|
|
// 출금 (withdrawals)
|
|
$withdrawalStats = DB::connection('mysql')
|
|
->table('withdrawals')
|
|
->where('tenant_id', $tenantId)
|
|
->where('withdrawal_date', $dateStr)
|
|
->whereNull('deleted_at')
|
|
->selectRaw('COUNT(*) as cnt, COALESCE(SUM(amount), 0) as total')
|
|
->first();
|
|
|
|
// 매입 (purchases)
|
|
$purchaseStats = DB::connection('mysql')
|
|
->table('purchases')
|
|
->where('tenant_id', $tenantId)
|
|
->where('purchase_date', $dateStr)
|
|
->whereNull('deleted_at')
|
|
->selectRaw('
|
|
COUNT(*) as cnt,
|
|
COALESCE(SUM(supply_amount), 0) as supply_total,
|
|
COALESCE(SUM(tax_amount), 0) as tax_total
|
|
')
|
|
->first();
|
|
|
|
// 어음 발행 (bills - issued on this date)
|
|
$billIssuedStats = DB::connection('mysql')
|
|
->table('bills')
|
|
->where('tenant_id', $tenantId)
|
|
->where('issue_date', $dateStr)
|
|
->whereNull('deleted_at')
|
|
->selectRaw('COUNT(*) as cnt, COALESCE(SUM(amount), 0) as total')
|
|
->first();
|
|
|
|
// 어음 만기 (bills - matured on this date)
|
|
$billMaturedStats = DB::connection('mysql')
|
|
->table('bills')
|
|
->where('tenant_id', $tenantId)
|
|
->where('maturity_date', $dateStr)
|
|
->whereNull('deleted_at')
|
|
->selectRaw('COUNT(*) as cnt, COALESCE(SUM(amount), 0) as total')
|
|
->first();
|
|
|
|
// 카드 거래 (withdrawals with card_id)
|
|
$cardStats = DB::connection('mysql')
|
|
->table('withdrawals')
|
|
->where('tenant_id', $tenantId)
|
|
->where('withdrawal_date', $dateStr)
|
|
->whereNotNull('card_id')
|
|
->whereNull('deleted_at')
|
|
->selectRaw('COUNT(*) as cnt, COALESCE(SUM(amount), 0) as total')
|
|
->first();
|
|
|
|
// 은행 잔액 합계 (bank_transactions - 계좌별 최신 잔액)
|
|
$bankBalance = DB::connection('mysql')
|
|
->query()
|
|
->fromSub(function ($query) use ($tenantId, $dateStr) {
|
|
$query->from('bank_transactions')
|
|
->where('tenant_id', $tenantId)
|
|
->where('transaction_date', '<=', $dateStr)
|
|
->whereNull('deleted_at')
|
|
->selectRaw('bank_account_id, balance_after as latest_balance,
|
|
ROW_NUMBER() OVER(PARTITION BY bank_account_id ORDER BY transaction_date DESC, id DESC) as rn');
|
|
}, 'sub')
|
|
->where('rn', 1)
|
|
->selectRaw('COALESCE(SUM(latest_balance), 0) as total_balance')
|
|
->first();
|
|
|
|
$depositAmount = $depositStats->total ?? 0;
|
|
$withdrawalAmount = $withdrawalStats->total ?? 0;
|
|
|
|
StatFinanceDaily::updateOrCreate(
|
|
['tenant_id' => $tenantId, 'stat_date' => $dateStr],
|
|
[
|
|
'deposit_count' => $depositStats->cnt ?? 0,
|
|
'deposit_amount' => $depositAmount,
|
|
'withdrawal_count' => $withdrawalStats->cnt ?? 0,
|
|
'withdrawal_amount' => $withdrawalAmount,
|
|
'net_cashflow' => $depositAmount - $withdrawalAmount,
|
|
'purchase_count' => $purchaseStats->cnt ?? 0,
|
|
'purchase_amount' => $purchaseStats->supply_total ?? 0,
|
|
'purchase_tax_amount' => $purchaseStats->tax_total ?? 0,
|
|
'receivable_balance' => 0, // Phase 3에서 미수금 로직 추가
|
|
'payable_balance' => 0,
|
|
'overdue_receivable' => 0,
|
|
'bill_issued_count' => $billIssuedStats->cnt ?? 0,
|
|
'bill_issued_amount' => $billIssuedStats->total ?? 0,
|
|
'bill_matured_count' => $billMaturedStats->cnt ?? 0,
|
|
'bill_matured_amount' => $billMaturedStats->total ?? 0,
|
|
'card_transaction_count' => $cardStats->cnt ?? 0,
|
|
'card_transaction_amount' => $cardStats->total ?? 0,
|
|
'bank_balance_total' => $bankBalance->total_balance ?? 0,
|
|
]
|
|
);
|
|
|
|
return 1;
|
|
}
|
|
|
|
public function aggregateMonthly(int $tenantId, int $year, int $month): int
|
|
{
|
|
$dailySum = StatFinanceDaily::where('tenant_id', $tenantId)
|
|
->whereYear('stat_date', $year)
|
|
->whereMonth('stat_date', $month)
|
|
->selectRaw('
|
|
SUM(deposit_amount) as deposit_total,
|
|
SUM(withdrawal_amount) as withdrawal_total,
|
|
SUM(net_cashflow) as net_cashflow,
|
|
SUM(purchase_amount) as purchase_total,
|
|
SUM(card_transaction_amount) as card_total
|
|
')
|
|
->first();
|
|
|
|
// 월말 데이터 (해당 월의 마지막 일간 레코드)
|
|
$lastDay = StatFinanceDaily::where('tenant_id', $tenantId)
|
|
->whereYear('stat_date', $year)
|
|
->whereMonth('stat_date', $month)
|
|
->orderByDesc('stat_date')
|
|
->first();
|
|
|
|
// 전월 대비 현금흐름 변화
|
|
$prevMonth = StatFinanceMonthly::where('tenant_id', $tenantId)
|
|
->where(function ($q) use ($year, $month) {
|
|
$prev = Carbon::create($year, $month, 1)->subMonth();
|
|
$q->where('stat_year', $prev->year)->where('stat_month', $prev->month);
|
|
})
|
|
->first();
|
|
|
|
$netCashflow = $dailySum->net_cashflow ?? 0;
|
|
$momChange = null;
|
|
if ($prevMonth && $prevMonth->net_cashflow != 0) {
|
|
$momChange = (($netCashflow - $prevMonth->net_cashflow) / abs($prevMonth->net_cashflow)) * 100;
|
|
}
|
|
|
|
StatFinanceMonthly::updateOrCreate(
|
|
['tenant_id' => $tenantId, 'stat_year' => $year, 'stat_month' => $month],
|
|
[
|
|
'deposit_total' => $dailySum->deposit_total ?? 0,
|
|
'withdrawal_total' => $dailySum->withdrawal_total ?? 0,
|
|
'net_cashflow' => $netCashflow,
|
|
'purchase_total' => $dailySum->purchase_total ?? 0,
|
|
'card_total' => $dailySum->card_total ?? 0,
|
|
'receivable_end' => $lastDay->receivable_balance ?? 0,
|
|
'payable_end' => $lastDay->payable_balance ?? 0,
|
|
'bank_balance_end' => $lastDay->bank_balance_total ?? 0,
|
|
'mom_cashflow_change' => $momChange,
|
|
]
|
|
);
|
|
|
|
return 1;
|
|
}
|
|
}
|