Files
sam-api/app/Services/Stats/QuoteStatService.php
권혁성 d7ca8cfa00 refactor:견적 converted 상태를 데이터 기반(order_id)으로 변경
- Quote 모델에 getStatusAttribute() accessor 추가: order_id 존재 시 자동으로 'converted' 반환
- scopeConverted() → whereNotNull('order_id') 변경
- QuoteService/OrderService에서 status='converted' 직접 세팅 제거, order_id만 세팅
- 상태 필터 쿼리: converted는 order_id IS NOT NULL 기반
- 통계 쿼리: status='converted' → order_id IS NOT NULL
- 수주 직접 등록 시에도 자동으로 수주전환 상태 반영

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 04:19:47 +09:00

94 lines
3.7 KiB
PHP

<?php
namespace App\Services\Stats;
use App\Models\Stats\Daily\StatQuotePipelineDaily;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
class QuoteStatService implements StatDomainServiceInterface
{
public function aggregateDaily(int $tenantId, Carbon $date): int
{
$dateStr = $date->format('Y-m-d');
// 견적 (quotes)
$quoteStats = DB::connection('mysql')
->table('quotes')
->where('tenant_id', $tenantId)
->where('registration_date', $dateStr)
->whereNull('deleted_at')
->selectRaw("
COUNT(*) as created_count,
COALESCE(SUM(total_amount), 0) as total_amount,
SUM(CASE WHEN status = 'approved' THEN 1 ELSE 0 END) as approved_count,
SUM(CASE WHEN status = 'rejected' THEN 1 ELSE 0 END) as rejected_count,
SUM(CASE WHEN order_id IS NOT NULL THEN 1 ELSE 0 END) as conversion_count
")
->first();
$createdCount = $quoteStats->created_count ?? 0;
$conversionCount = $quoteStats->conversion_count ?? 0;
$conversionRate = $createdCount > 0 ? ($conversionCount / $createdCount) * 100 : 0;
// 입찰 (biddings)
$biddingStats = DB::connection('mysql')
->table('biddings')
->where('tenant_id', $tenantId)
->where('bidding_date', $dateStr)
->whereNull('deleted_at')
->selectRaw("
COUNT(*) as cnt,
SUM(CASE WHEN status = 'won' THEN 1 ELSE 0 END) as won_count,
COALESCE(SUM(bidding_amount), 0) as total_amount
")
->first();
// 상담 (sales_prospect_consultations)
$consultationCount = DB::connection('mysql')
->table('sales_prospect_consultations')
->whereDate('created_at', $dateStr)
->count();
// 영업 기회 (sales_prospects - tenant_id 없음, created_at 기반)
$prospectStats = DB::connection('mysql')
->table('sales_prospects')
->whereDate('created_at', $dateStr)
->whereNull('deleted_at')
->selectRaw("
COUNT(*) as created_count,
SUM(CASE WHEN status = 'contracted' THEN 1 ELSE 0 END) as won_count,
SUM(CASE WHEN status = 'lost' THEN 1 ELSE 0 END) as lost_count
")
->first();
StatQuotePipelineDaily::updateOrCreate(
['tenant_id' => $tenantId, 'stat_date' => $dateStr],
[
'quote_created_count' => $createdCount,
'quote_amount' => $quoteStats->total_amount ?? 0,
'quote_approved_count' => $quoteStats->approved_count ?? 0,
'quote_rejected_count' => $quoteStats->rejected_count ?? 0,
'quote_conversion_count' => $conversionCount,
'quote_conversion_rate' => $conversionRate,
'prospect_created_count' => $prospectStats->created_count ?? 0,
'prospect_won_count' => $prospectStats->won_count ?? 0,
'prospect_lost_count' => $prospectStats->lost_count ?? 0,
'prospect_amount' => 0, // sales_prospects에 금액 컬럼 없음
'bidding_count' => $biddingStats->cnt ?? 0,
'bidding_won_count' => $biddingStats->won_count ?? 0,
'bidding_amount' => $biddingStats->total_amount ?? 0,
'consultation_count' => $consultationCount,
]
);
return 1;
}
public function aggregateMonthly(int $tenantId, int $year, int $month): int
{
// 견적 도메인은 일간 테이블만 운영 (월간은 Phase 4에서 필요시 추가)
return 0;
}
}