193 lines
7.9 KiB
PHP
193 lines
7.9 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace App\Services\Stats;
|
||
|
|
|
||
|
|
use App\Models\Stats\Daily\StatSalesDaily;
|
||
|
|
use App\Models\Stats\Monthly\StatSalesMonthly;
|
||
|
|
use Carbon\Carbon;
|
||
|
|
use Illuminate\Support\Facades\DB;
|
||
|
|
|
||
|
|
class SalesStatService implements StatDomainServiceInterface
|
||
|
|
{
|
||
|
|
public function aggregateDaily(int $tenantId, Carbon $date): int
|
||
|
|
{
|
||
|
|
$dateStr = $date->format('Y-m-d');
|
||
|
|
|
||
|
|
// 수주 집계 (orders)
|
||
|
|
$orderStats = DB::connection('mysql')
|
||
|
|
->table('orders')
|
||
|
|
->where('tenant_id', $tenantId)
|
||
|
|
->whereDate('created_at', $dateStr)
|
||
|
|
->whereNull('deleted_at')
|
||
|
|
->selectRaw('
|
||
|
|
COUNT(*) as order_count,
|
||
|
|
COALESCE(SUM(total_amount), 0) as order_amount,
|
||
|
|
COALESCE(SUM(quantity), 0) as order_item_count,
|
||
|
|
SUM(CASE WHEN status_code = ? THEN 1 ELSE 0 END) as order_draft_count,
|
||
|
|
SUM(CASE WHEN status_code = ? THEN 1 ELSE 0 END) as order_confirmed_count,
|
||
|
|
SUM(CASE WHEN status_code = ? THEN 1 ELSE 0 END) as order_in_progress_count,
|
||
|
|
SUM(CASE WHEN status_code = ? THEN 1 ELSE 0 END) as order_completed_count,
|
||
|
|
SUM(CASE WHEN status_code = ? THEN 1 ELSE 0 END) as order_cancelled_count
|
||
|
|
', ['draft', 'confirmed', 'in_progress', 'completed', 'cancelled'])
|
||
|
|
->first();
|
||
|
|
|
||
|
|
// 매출 집계 (sales)
|
||
|
|
$salesStats = DB::connection('mysql')
|
||
|
|
->table('sales')
|
||
|
|
->where('tenant_id', $tenantId)
|
||
|
|
->where('sale_date', $dateStr)
|
||
|
|
->whereNull('deleted_at')
|
||
|
|
->selectRaw('
|
||
|
|
COUNT(*) as sales_count,
|
||
|
|
COALESCE(SUM(supply_amount), 0) as sales_amount,
|
||
|
|
COALESCE(SUM(tax_amount), 0) as sales_tax_amount
|
||
|
|
')
|
||
|
|
->first();
|
||
|
|
|
||
|
|
// 신규 고객 (당일 생성된 고객)
|
||
|
|
$newClientCount = DB::connection('mysql')
|
||
|
|
->table('clients')
|
||
|
|
->where('tenant_id', $tenantId)
|
||
|
|
->whereDate('created_at', $dateStr)
|
||
|
|
->count();
|
||
|
|
|
||
|
|
// 활성 고객 (당일 수주/매출에 연결된 고유 고객)
|
||
|
|
$activeClientCount = DB::connection('mysql')
|
||
|
|
->table('orders')
|
||
|
|
->where('tenant_id', $tenantId)
|
||
|
|
->whereDate('created_at', $dateStr)
|
||
|
|
->whereNull('deleted_at')
|
||
|
|
->whereNotNull('client_id')
|
||
|
|
->distinct('client_id')
|
||
|
|
->count('client_id');
|
||
|
|
|
||
|
|
// 출하 집계 (shipments)
|
||
|
|
$shipmentStats = DB::connection('mysql')
|
||
|
|
->table('shipments')
|
||
|
|
->where('tenant_id', $tenantId)
|
||
|
|
->where('scheduled_date', $dateStr)
|
||
|
|
->where('status', 'completed')
|
||
|
|
->whereNull('deleted_at')
|
||
|
|
->selectRaw('
|
||
|
|
COUNT(*) as shipment_count,
|
||
|
|
COALESCE(SUM(shipping_cost), 0) as shipment_amount
|
||
|
|
')
|
||
|
|
->first();
|
||
|
|
|
||
|
|
StatSalesDaily::updateOrCreate(
|
||
|
|
['tenant_id' => $tenantId, 'stat_date' => $dateStr],
|
||
|
|
[
|
||
|
|
'order_count' => $orderStats->order_count ?? 0,
|
||
|
|
'order_amount' => $orderStats->order_amount ?? 0,
|
||
|
|
'order_item_count' => $orderStats->order_item_count ?? 0,
|
||
|
|
'sales_count' => $salesStats->sales_count ?? 0,
|
||
|
|
'sales_amount' => $salesStats->sales_amount ?? 0,
|
||
|
|
'sales_tax_amount' => $salesStats->sales_tax_amount ?? 0,
|
||
|
|
'new_client_count' => $newClientCount,
|
||
|
|
'active_client_count' => $activeClientCount,
|
||
|
|
'order_draft_count' => $orderStats->order_draft_count ?? 0,
|
||
|
|
'order_confirmed_count' => $orderStats->order_confirmed_count ?? 0,
|
||
|
|
'order_in_progress_count' => $orderStats->order_in_progress_count ?? 0,
|
||
|
|
'order_completed_count' => $orderStats->order_completed_count ?? 0,
|
||
|
|
'order_cancelled_count' => $orderStats->order_cancelled_count ?? 0,
|
||
|
|
'shipment_count' => $shipmentStats->shipment_count ?? 0,
|
||
|
|
'shipment_amount' => $shipmentStats->shipment_amount ?? 0,
|
||
|
|
]
|
||
|
|
);
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function aggregateMonthly(int $tenantId, int $year, int $month): int
|
||
|
|
{
|
||
|
|
// 일간 데이터를 합산하여 월간 통계 생성
|
||
|
|
$dailySum = StatSalesDaily::where('tenant_id', $tenantId)
|
||
|
|
->whereYear('stat_date', $year)
|
||
|
|
->whereMonth('stat_date', $month)
|
||
|
|
->selectRaw('
|
||
|
|
SUM(order_count) as order_count,
|
||
|
|
SUM(order_amount) as order_amount,
|
||
|
|
SUM(sales_count) as sales_count,
|
||
|
|
SUM(sales_amount) as sales_amount,
|
||
|
|
SUM(shipment_count) as shipment_count,
|
||
|
|
SUM(shipment_amount) as shipment_amount
|
||
|
|
')
|
||
|
|
->first();
|
||
|
|
|
||
|
|
// 월간 고유 거래 고객 수
|
||
|
|
$startDate = Carbon::create($year, $month, 1)->format('Y-m-d');
|
||
|
|
$endDate = Carbon::create($year, $month, 1)->endOfMonth()->format('Y-m-d');
|
||
|
|
|
||
|
|
$uniqueClientCount = DB::connection('mysql')
|
||
|
|
->table('orders')
|
||
|
|
->where('tenant_id', $tenantId)
|
||
|
|
->whereBetween(DB::raw('DATE(created_at)'), [$startDate, $endDate])
|
||
|
|
->whereNull('deleted_at')
|
||
|
|
->whereNotNull('client_id')
|
||
|
|
->distinct('client_id')
|
||
|
|
->count('client_id');
|
||
|
|
|
||
|
|
// 평균 수주 금액
|
||
|
|
$orderCount = $dailySum->order_count ?? 0;
|
||
|
|
$orderAmount = $dailySum->order_amount ?? 0;
|
||
|
|
$avgOrderAmount = $orderCount > 0 ? $orderAmount / $orderCount : 0;
|
||
|
|
|
||
|
|
// 최다 거래 고객
|
||
|
|
$topClient = DB::connection('mysql')
|
||
|
|
->table('orders')
|
||
|
|
->where('tenant_id', $tenantId)
|
||
|
|
->whereBetween(DB::raw('DATE(created_at)'), [$startDate, $endDate])
|
||
|
|
->whereNull('deleted_at')
|
||
|
|
->whereNotNull('client_id')
|
||
|
|
->groupBy('client_id')
|
||
|
|
->orderByRaw('SUM(total_amount) DESC')
|
||
|
|
->selectRaw('client_id, SUM(total_amount) as total')
|
||
|
|
->first();
|
||
|
|
|
||
|
|
// 전월 대비 성장률
|
||
|
|
$prevMonth = StatSalesMonthly::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();
|
||
|
|
|
||
|
|
$salesAmount = $dailySum->sales_amount ?? 0;
|
||
|
|
$momGrowthRate = null;
|
||
|
|
if ($prevMonth && $prevMonth->sales_amount > 0) {
|
||
|
|
$momGrowthRate = (($salesAmount - $prevMonth->sales_amount) / $prevMonth->sales_amount) * 100;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 전년동월 대비
|
||
|
|
$prevYear = StatSalesMonthly::where('tenant_id', $tenantId)
|
||
|
|
->where('stat_year', $year - 1)
|
||
|
|
->where('stat_month', $month)
|
||
|
|
->first();
|
||
|
|
|
||
|
|
$yoyGrowthRate = null;
|
||
|
|
if ($prevYear && $prevYear->sales_amount > 0) {
|
||
|
|
$yoyGrowthRate = (($salesAmount - $prevYear->sales_amount) / $prevYear->sales_amount) * 100;
|
||
|
|
}
|
||
|
|
|
||
|
|
StatSalesMonthly::updateOrCreate(
|
||
|
|
['tenant_id' => $tenantId, 'stat_year' => $year, 'stat_month' => $month],
|
||
|
|
[
|
||
|
|
'order_count' => $dailySum->order_count ?? 0,
|
||
|
|
'order_amount' => $orderAmount,
|
||
|
|
'sales_count' => $dailySum->sales_count ?? 0,
|
||
|
|
'sales_amount' => $salesAmount,
|
||
|
|
'shipment_count' => $dailySum->shipment_count ?? 0,
|
||
|
|
'shipment_amount' => $dailySum->shipment_amount ?? 0,
|
||
|
|
'unique_client_count' => $uniqueClientCount,
|
||
|
|
'avg_order_amount' => $avgOrderAmount,
|
||
|
|
'top_client_id' => $topClient->client_id ?? null,
|
||
|
|
'top_client_amount' => $topClient->total ?? 0,
|
||
|
|
'mom_growth_rate' => $momGrowthRate,
|
||
|
|
'yoy_growth_rate' => $yoyGrowthRate,
|
||
|
|
]
|
||
|
|
);
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
}
|