feat: sam_stat P1 도메인 확장 (Phase 3)
- 차원 테이블: dim_client, dim_product 마이그레이션 + SCD Type 2 동기화 (DimensionSyncService) - 재고 통계: stat_inventory_daily + InventoryStatService (stocks, stock_transactions, inspections) - 견적/영업 통계: stat_quote_pipeline_daily + QuoteStatService (quotes, biddings, sales_prospects) - 인사/근태 통계: stat_hr_attendance_daily + HrStatService (attendances, leaves, user_tenants) - KPI/알림: stat_kpi_targets, stat_alerts + KpiAlertService + StatCheckKpiAlertsCommand - StatAggregatorService에 inventory, quote, hr 도메인 추가 (총 6개 도메인) - 스케줄러: stat:check-kpi-alerts 매일 09:00 등록 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
163
app/Services/Stats/DimensionSyncService.php
Normal file
163
app/Services/Stats/DimensionSyncService.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Stats;
|
||||
|
||||
use App\Models\Stats\Dimensions\DimClient;
|
||||
use App\Models\Stats\Dimensions\DimProduct;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class DimensionSyncService
|
||||
{
|
||||
/**
|
||||
* 고객 차원 동기화 (SCD Type 2)
|
||||
*/
|
||||
public function syncClients(int $tenantId): int
|
||||
{
|
||||
$today = Carbon::today()->format('Y-m-d');
|
||||
$synced = 0;
|
||||
|
||||
$clients = DB::connection('mysql')
|
||||
->table('clients')
|
||||
->where('tenant_id', $tenantId)
|
||||
->select('id', 'tenant_id', 'name', 'client_group_id', 'client_type')
|
||||
->get();
|
||||
|
||||
foreach ($clients as $client) {
|
||||
$groupName = null;
|
||||
if ($client->client_group_id) {
|
||||
$groupName = DB::connection('mysql')
|
||||
->table('client_groups')
|
||||
->where('id', $client->client_group_id)
|
||||
->value('group_name');
|
||||
}
|
||||
|
||||
$current = DimClient::where('tenant_id', $tenantId)
|
||||
->where('client_id', $client->id)
|
||||
->where('is_current', true)
|
||||
->first();
|
||||
|
||||
if (! $current) {
|
||||
DimClient::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'client_id' => $client->id,
|
||||
'client_name' => $client->name,
|
||||
'client_group_id' => $client->client_group_id,
|
||||
'client_group_name' => $groupName,
|
||||
'client_type' => $client->client_type,
|
||||
'region' => null,
|
||||
'valid_from' => $today,
|
||||
'valid_to' => null,
|
||||
'is_current' => true,
|
||||
]);
|
||||
$synced++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$changed = $current->client_name !== $client->name
|
||||
|| $current->client_group_id != $client->client_group_id
|
||||
|| $current->client_type !== $client->client_type;
|
||||
|
||||
if ($changed) {
|
||||
$current->update([
|
||||
'valid_to' => $today,
|
||||
'is_current' => false,
|
||||
]);
|
||||
|
||||
DimClient::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'client_id' => $client->id,
|
||||
'client_name' => $client->name,
|
||||
'client_group_id' => $client->client_group_id,
|
||||
'client_group_name' => $groupName,
|
||||
'client_type' => $client->client_type,
|
||||
'region' => null,
|
||||
'valid_from' => $today,
|
||||
'valid_to' => null,
|
||||
'is_current' => true,
|
||||
]);
|
||||
$synced++;
|
||||
}
|
||||
}
|
||||
|
||||
return $synced;
|
||||
}
|
||||
|
||||
/**
|
||||
* 제품(품목) 차원 동기화 (SCD Type 2)
|
||||
*/
|
||||
public function syncProducts(int $tenantId): int
|
||||
{
|
||||
$today = Carbon::today()->format('Y-m-d');
|
||||
$synced = 0;
|
||||
|
||||
$items = DB::connection('mysql')
|
||||
->table('items')
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('is_active', true)
|
||||
->select('id', 'tenant_id', 'code', 'name', 'item_type', 'category_id')
|
||||
->get();
|
||||
|
||||
foreach ($items as $item) {
|
||||
$categoryName = null;
|
||||
if ($item->category_id) {
|
||||
$categoryName = DB::connection('mysql')
|
||||
->table('categories')
|
||||
->where('id', $item->category_id)
|
||||
->value('name');
|
||||
}
|
||||
|
||||
$current = DimProduct::where('tenant_id', $tenantId)
|
||||
->where('item_id', $item->id)
|
||||
->where('is_current', true)
|
||||
->first();
|
||||
|
||||
if (! $current) {
|
||||
DimProduct::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'item_id' => $item->id,
|
||||
'item_code' => $item->code,
|
||||
'item_name' => $item->name,
|
||||
'item_type' => $item->item_type,
|
||||
'category_id' => $item->category_id,
|
||||
'category_name' => $categoryName,
|
||||
'valid_from' => $today,
|
||||
'valid_to' => null,
|
||||
'is_current' => true,
|
||||
]);
|
||||
$synced++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$changed = $current->item_name !== $item->name
|
||||
|| $current->item_code !== $item->code
|
||||
|| $current->item_type !== $item->item_type
|
||||
|| $current->category_id != $item->category_id;
|
||||
|
||||
if ($changed) {
|
||||
$current->update([
|
||||
'valid_to' => $today,
|
||||
'is_current' => false,
|
||||
]);
|
||||
|
||||
DimProduct::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'item_id' => $item->id,
|
||||
'item_code' => $item->code,
|
||||
'item_name' => $item->name,
|
||||
'item_type' => $item->item_type,
|
||||
'category_id' => $item->category_id,
|
||||
'category_name' => $categoryName,
|
||||
'valid_from' => $today,
|
||||
'valid_to' => null,
|
||||
'is_current' => true,
|
||||
]);
|
||||
$synced++;
|
||||
}
|
||||
}
|
||||
|
||||
return $synced;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user