Files
sam-api/app/Services/Stats/ProjectStatService.php
권혁성 4d8dac1091 feat: sam_stat P2 도메인 + 통계 API + 대시보드 전환 (Phase 4)
- 4.1: stat_project_monthly + ProjectStatService (건설/프로젝트 월간)
- 4.2: stat_system_daily + SystemStatService (API/감사/FCM/파일/결재)
- 4.3: stat_events, stat_snapshots + StatEventService + StatEventObserver
- 4.4: StatController (summary/daily/monthly/alerts) + StatQueryService + FormRequest 3개 + routes/stats.php
- 4.5: DashboardService sam_stat 우선 조회 + 원본 DB 폴백 패턴

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 21:56:53 +09:00

93 lines
3.2 KiB
PHP

<?php
namespace App\Services\Stats;
use App\Models\Stats\Monthly\StatProjectMonthly;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
class ProjectStatService implements StatDomainServiceInterface
{
public function aggregateDaily(int $tenantId, Carbon $date): int
{
// 건설/프로젝트는 월간 전용 (일간 집계 없음)
return 0;
}
public function aggregateMonthly(int $tenantId, int $year, int $month): int
{
$startDate = Carbon::create($year, $month, 1)->startOfDay();
$endDate = $startDate->copy()->endOfMonth();
// 현장 현황
$activeSites = DB::connection('mysql')
->table('sites')
->where('tenant_id', $tenantId)
->where('status', 'active')
->whereNull('deleted_at')
->count();
$completedSites = DB::connection('mysql')
->table('sites')
->where('tenant_id', $tenantId)
->where('status', 'completed')
->whereNull('deleted_at')
->count();
// 계약 현황 (해당 월 신규 계약)
$contractStats = DB::connection('mysql')
->table('contracts')
->where('tenant_id', $tenantId)
->whereBetween('created_at', [$startDate, $endDate])
->whereNull('deleted_at')
->selectRaw('
COUNT(*) as new_count,
COALESCE(SUM(contract_amount), 0) as total_amount
')
->first();
// 지출예상 (해당 월)
$expenseStats = DB::connection('mysql')
->table('expected_expenses')
->where('tenant_id', $tenantId)
->whereYear('expected_payment_date', $year)
->whereMonth('expected_payment_date', $month)
->whereNull('deleted_at')
->selectRaw('
COALESCE(SUM(amount), 0) as expected_total,
COALESCE(SUM(CASE WHEN payment_status = \'paid\' THEN amount ELSE 0 END), 0) as actual_total
')
->first();
// 수익률 계산
$contractTotal = (float) ($contractStats->total_amount ?? 0);
$actualExpense = (float) ($expenseStats->actual_total ?? 0);
$grossProfit = $contractTotal - $actualExpense;
$grossProfitRate = $contractTotal > 0 ? ($grossProfit / $contractTotal) * 100 : 0;
StatProjectMonthly::updateOrCreate(
[
'tenant_id' => $tenantId,
'stat_year' => $year,
'stat_month' => $month,
],
[
'active_site_count' => $activeSites,
'completed_site_count' => $completedSites,
'new_contract_count' => $contractStats->new_count ?? 0,
'contract_total_amount' => $contractTotal,
'expected_expense_total' => $expenseStats->expected_total ?? 0,
'actual_expense_total' => $actualExpense,
'labor_cost_total' => 0,
'material_cost_total' => 0,
'gross_profit' => $grossProfit,
'gross_profit_rate' => $grossProfitRate,
'handover_report_count' => 0,
'structure_review_count' => 0,
]
);
return 1;
}
}