feat: sam_stat P0 도메인 집계 구현 (Phase 2)
- 영업(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>
This commit is contained in:
172
app/Services/Stats/FinanceStatService.php
Normal file
172
app/Services/Stats/FinanceStatService.php
Normal file
@@ -0,0 +1,172 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user