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>
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'sam_stat';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::connection('sam_stat')->create('stat_project_monthly', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id');
|
||||
$table->smallInteger('stat_year');
|
||||
$table->tinyInteger('stat_month');
|
||||
|
||||
// 프로젝트 현황
|
||||
$table->unsignedInteger('active_site_count')->default(0);
|
||||
$table->unsignedInteger('completed_site_count')->default(0);
|
||||
$table->unsignedInteger('new_contract_count')->default(0);
|
||||
$table->decimal('contract_total_amount', 18, 2)->default(0);
|
||||
|
||||
// 원가
|
||||
$table->decimal('expected_expense_total', 18, 2)->default(0);
|
||||
$table->decimal('actual_expense_total', 18, 2)->default(0);
|
||||
$table->decimal('labor_cost_total', 18, 2)->default(0);
|
||||
$table->decimal('material_cost_total', 18, 2)->default(0);
|
||||
|
||||
// 수익률
|
||||
$table->decimal('gross_profit', 18, 2)->default(0);
|
||||
$table->decimal('gross_profit_rate', 5, 2)->default(0);
|
||||
|
||||
// 이슈
|
||||
$table->unsignedInteger('handover_report_count')->default(0);
|
||||
$table->unsignedInteger('structure_review_count')->default(0);
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['tenant_id', 'stat_year', 'stat_month'], 'uk_tenant_year_month');
|
||||
$table->index(['stat_year', 'stat_month'], 'idx_year_month');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection('sam_stat')->dropIfExists('stat_project_monthly');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'sam_stat';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::connection('sam_stat')->create('stat_system_daily', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id');
|
||||
$table->date('stat_date');
|
||||
|
||||
// API 사용량
|
||||
$table->unsignedInteger('api_request_count')->default(0);
|
||||
$table->unsignedInteger('api_error_count')->default(0);
|
||||
$table->unsignedInteger('api_avg_response_ms')->default(0);
|
||||
|
||||
// 사용자 활동
|
||||
$table->unsignedInteger('active_user_count')->default(0);
|
||||
$table->unsignedInteger('login_count')->default(0);
|
||||
|
||||
// 감사
|
||||
$table->unsignedInteger('audit_create_count')->default(0);
|
||||
$table->unsignedInteger('audit_update_count')->default(0);
|
||||
$table->unsignedInteger('audit_delete_count')->default(0);
|
||||
|
||||
// 알림
|
||||
$table->unsignedInteger('fcm_sent_count')->default(0);
|
||||
$table->unsignedInteger('fcm_failed_count')->default(0);
|
||||
|
||||
// 파일
|
||||
$table->unsignedInteger('file_upload_count')->default(0);
|
||||
$table->decimal('file_upload_size_mb', 10, 2)->default(0);
|
||||
|
||||
// 결재
|
||||
$table->unsignedInteger('approval_submitted_count')->default(0);
|
||||
$table->unsignedInteger('approval_completed_count')->default(0);
|
||||
$table->decimal('approval_avg_hours', 8, 2)->default(0);
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['tenant_id', 'stat_date'], 'uk_tenant_date');
|
||||
$table->index('stat_date', 'idx_date');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection('sam_stat')->dropIfExists('stat_system_daily');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'sam_stat';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::connection('sam_stat')->create('stat_events', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id');
|
||||
$table->string('domain', 50);
|
||||
$table->string('event_type', 100);
|
||||
$table->string('entity_type', 100);
|
||||
$table->unsignedBigInteger('entity_id');
|
||||
$table->json('payload')->nullable();
|
||||
$table->timestamp('occurred_at');
|
||||
|
||||
$table->index(['tenant_id', 'domain'], 'idx_tenant_domain');
|
||||
$table->index('occurred_at', 'idx_occurred');
|
||||
$table->index(['entity_type', 'entity_id'], 'idx_entity');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection('sam_stat')->dropIfExists('stat_events');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
protected $connection = 'sam_stat';
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
Schema::connection('sam_stat')->create('stat_snapshots', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id');
|
||||
$table->date('snapshot_date');
|
||||
$table->string('domain', 50);
|
||||
$table->string('snapshot_type', 50)->default('daily');
|
||||
$table->json('data');
|
||||
$table->timestamp('created_at')->nullable();
|
||||
|
||||
$table->unique(['tenant_id', 'snapshot_date', 'domain', 'snapshot_type'], 'uk_tenant_date_domain');
|
||||
$table->index('snapshot_date', 'idx_date');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection('sam_stat')->dropIfExists('stat_snapshots');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user