From c526c59e428dd22627836498cd767185522e6c91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Thu, 29 Jan 2026 17:13:42 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20sam=5Fstat=20=ED=86=B5=EA=B3=84=20DB=20?= =?UTF-8?q?=EC=84=A4=EA=B3=84=20=EA=B3=84=ED=9A=8D=EC=84=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(Phase=201=20=EC=99=84=EB=A3=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 8개 통계 도메인, 20개 테이블 설계 - Phase 1 인프라 구축 완료 상태 반영 Co-Authored-By: Claude Opus 4.5 --- plans/sam-stat-database-design-plan.md | 1282 ++++++++++++++++++++++++ 1 file changed, 1282 insertions(+) create mode 100644 plans/sam-stat-database-design-plan.md diff --git a/plans/sam-stat-database-design-plan.md b/plans/sam-stat-database-design-plan.md new file mode 100644 index 0000000..b3aca69 --- /dev/null +++ b/plans/sam-stat-database-design-plan.md @@ -0,0 +1,1282 @@ +# SAM 통계 시스템 (sam_stat DB) 설계 계획 + +> **작성일**: 2026-01-29 +> **목적**: SAM ERP의 확장 가능한 통계 전용 데이터베이스(sam_stat) 설계 +> **기준 문서**: `docs/specs/database-schema.md`, `docs/architecture/system-overview.md` +> **상태**: 📋 설계 제안 (검토 대기) + +--- + +## 📍 현재 진행 상태 + +| 항목 | 내용 | +|------|------| +| **마지막 완료 작업** | Phase 1: 인프라 구축 완료 (DB, 마이그레이션, 모델, 커맨드, 서비스) | +| **다음 작업** | Phase 2: P0 도메인 구축 (매출/재무/생산 집계) | +| **진행률** | 1/6 Phase (17%) | +| **마지막 업데이트** | 2026-01-29 | + +--- + +## 0. 프로젝트 컨텍스트 (새 세션용) + +> **이 섹션은 새 세션에서 이 문서만으로 작업을 시작할 수 있도록 필요한 모든 컨텍스트를 포함한다.** + +### 0.1 프로젝트 구조 + +``` +/Users/kent/Works/@KD_SAM/SAM/ +├── api/ ← 작업 대상 (Laravel 12 REST API, PHP 8.4+) +│ ├── app/ +│ │ ├── Console/Commands/ # Artisan 커맨드 (19개 존재) +│ │ ├── Http/Controllers/Api/V1/ # API 컨트롤러 +│ │ ├── Models/ # Eloquent 모델 (167개) +│ │ │ ├── Stats/ # ← 새로 생성할 통계 모델 디렉토리 +│ │ │ ├── Tenants/ # 테넌트 스코프 모델 (가장 많음) +│ │ │ ├── Orders/ # 수주 관련 +│ │ │ ├── Production/ # 생산 관련 +│ │ │ └── ... +│ │ └── Services/ # 비즈니스 로직 (Service-First 아키텍처) +│ │ ├── Stats/ # ← 새로 생성할 통계 서비스 디렉토리 +│ │ ├── DashboardService.php # 기존 대시보드 (355줄, 원본 DB 실시간 집계) +│ │ ├── ReportService.php # 기존 보고서 (일일일보, 지출예상) +│ │ ├── DailyReportService.php # 일일 보고서 (어음, 계좌, 요약) +│ │ ├── AiReportService.php # AI 보고서 +│ │ └── ... +│ ├── config/ +│ │ └── database.php # DB 연결 설정 (mysql, chandj 존재) +│ ├── database/ +│ │ └── migrations/ # 279개 마이그레이션 파일 +│ ├── routes/ +│ │ ├── console.php # 스케줄러 정의 (Laravel 12 방식) +│ │ └── api/v1/ +│ │ ├── common.php # dashboard, reports 라우트 +│ │ ├── finance.php # daily-report 라우트 +│ │ └── ... # 14개 라우트 파일 +│ └── .env # 환경변수 +├── mng/ # 관리자 패널 (Plain Laravel + Blade/Tailwind) +├── react/ # Next.js 15 프론트엔드 +├── docker/ +│ └── docker-compose.yml # Docker 설정 +└── docs/ # 기술 문서 + ├── specs/database-schema.md # DB 스키마 문서 + ├── architecture/system-overview.md + └── plans/ # 이 문서의 위치 +``` + +### 0.2 현재 DB 환경 + +``` +# .env (api/) +DB_CONNECTION=mysql +DB_HOST=127.0.0.1 # Docker 내부: sam-mysql-1 +DB_PORT=3306 +DB_DATABASE=samdb # ← 원본 DB (219개 테이블) +DB_USERNAME=samuser +DB_PASSWORD=sampass + +# sam_stat 연결은 아직 없음 → Phase 1에서 추가 +``` + +**config/database.php 현재 연결:** +- `mysql` - 기본 samdb (원본) +- `chandj` - 5130 레거시 DB (사용하지 않음) +- `sam_stat` - **아직 없음** (이 작업에서 추가) + +### 0.3 기존 대시보드/보고서 시스템 (변경 대상) + +| 파일 | 경로 | 역할 | 통계 전환 시 영향 | +|------|------|------|------------------| +| DashboardController | `api/app/Http/Controllers/Api/V1/DashboardController.php` | summary, charts, approvals | Phase 4.5에서 sam_stat 조회로 전환 | +| ReportController | `api/app/Http/Controllers/Api/V1/ReportController.php` | daily, expense-estimate, export | Phase 4.5에서 sam_stat 조회로 전환 | +| DailyReportController | `api/app/Http/Controllers/Api/V1/DailyReportController.php` | note-receivables, accounts, summary | Phase 4.5에서 sam_stat 조회로 전환 | +| DashboardService | `api/app/Services/DashboardService.php` (355줄) | 원본 DB에서 실시간 집계 (Attendance, Approval, Deposit, Sale 등) | **핵심 전환 대상** | +| ReportService | `api/app/Services/ReportService.php` | 일일일보, 지출예상 (Excel 내보내기 포함) | 부분 전환 | +| DailyReportService | `api/app/Services/DailyReportService.php` | 어음/외상채권, 계좌현황 | 부분 전환 | +| AiReportService | `api/app/Services/AiReportService.php` | AI 보고서 생성/조회 | 변경 없음 | + +**현재 API 라우트 (변경 없음, 내부 데이터소스만 전환):** +``` +# common.php +GET /api/v1/dashboard/summary → DashboardController@summary +GET /api/v1/dashboard/charts → DashboardController@charts +GET /api/v1/dashboard/approvals → DashboardController@approvals +GET /api/v1/reports/daily → ReportController@daily +GET /api/v1/reports/daily/export → ReportController@dailyExport +GET /api/v1/reports/expense-estimate → ReportController@expenseEstimate + +# finance.php +GET /api/v1/daily-report/note-receivables → DailyReportController@noteReceivables +GET /api/v1/daily-report/daily-accounts → DailyReportController@dailyAccounts +GET /api/v1/daily-report/summary → DailyReportController@summary +``` + +### 0.4 기존 스케줄러 패턴 (따라야 할 패턴) + +```php +// api/routes/console.php (Laravel 12 방식 - Kernel.php 없음) +use Illuminate\Support\Facades\Schedule; + +// 기존 스케줄러: 매일 03:00 API 로그 정리 +Schedule::command('api-log:prune') + ->dailyAt('03:00') + ->appendOutputTo(storage_path('logs/scheduler.log')) + ->onSuccess(function () { Log::info('...'); }) + ->onFailure(function () { Log::error('...'); }); +``` + +### 0.5 기존 Artisan 커맨드 패턴 + +``` +api/app/Console/Commands/ +├── PruneAuditLogs.php # 감사 로그 정리 (참고 패턴) +├── CleanupExpiredLinks.php # 만료 링크 정리 +├── RecordStorageUsage.php # 저장소 사용량 기록 +├── TenantsBootstrap.php # 테넌트 초기화 +└── ... # 총 19개 +``` + +### 0.6 모델 패턴 (따라야 할 패턴) + +```php +// 기존 모델 예시 - 멀티테넌트 + Soft Delete +namespace App\Models\Tenants; + +use App\Models\Scopes\TenantScope; +use Illuminate\Database\Eloquent\SoftDeletes; + +class Deposit extends Model +{ + use SoftDeletes; + + protected $table = 'deposits'; + + protected static function booted(): void + { + static::addGlobalScope(new TenantScope); + } +} + +// 통계 모델은 다른 DB 연결 사용 +// protected $connection = 'sam_stat'; +// TenantScope 대신 tenant_id를 직접 WHERE 조건으로 사용 +``` + +### 0.7 환경별 구성 + +#### 로컬 환경 (Docker) + +```yaml +# docker/docker-compose.yml 내 MySQL 서비스 +# Docker 내부 호스트: sam-mysql-1 +# sam_stat DB는 같은 MySQL 인스턴스에 생성 (별도 서버 불필요) +``` + +```bash +# 로컬 sam_stat DB 생성 +docker compose exec mysql mysql -u root -proot \ + -e "CREATE DATABASE sam_stat CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" + +# 로컬 마이그레이션 실행 +docker compose exec api php artisan migrate --database=sam_stat + +# 로컬 시딩 +docker compose exec api php artisan db:seed --class=DimDateSeeder +``` + +#### 개발 서버 (non-Docker, codebridge-x.com) + +> **개발 서버는 Docker를 사용하지 않는다.** +> 로컬에서 코드 작업 후 Git push하면 되지만, 개발 서버에서 아래 **1회 세팅이 필요**하다. + +```bash +# 1. sam_stat DB 생성 (개발 서버 MySQL 직접 접속) +mysql -u [user] -p \ + -e "CREATE DATABASE sam_stat CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" + +# 2. .env에 STAT_DB_* 환경변수 추가 (개발 서버의 api/.env) +# STAT_DB_HOST=127.0.0.1 +# STAT_DB_PORT=3306 +# STAT_DB_DATABASE=sam_stat +# STAT_DB_USERNAME=[개발서버 DB 유저] +# STAT_DB_PASSWORD=[개발서버 DB 비밀번호] + +# 3. 마이그레이션 실행 +cd /path/to/api && php artisan migrate --database=sam_stat + +# 4. dim_date 시딩 +php artisan db:seed --class=DimDateSeeder + +# 5. 스케줄러 cron 확인 (이미 등록되어 있다면 추가 불필요) +# * * * * * cd /path/to/api && php artisan schedule:run >> /dev/null 2>&1 +``` + +#### 배포 워크플로우 + +``` +로컬 (Docker, *.sam.kr) + ↓ Git push +개발 서버 (non-Docker, codebridge-x.com) + ↓ 수동 배포 + ↓ 최초 1회: DB 생성 + .env + migrate + seed + cron 확인 + ↓ 이후: git pull → php artisan migrate --database=sam_stat +운영 (TBD) +``` + +**코드에 커밋되는 것:** `config/database.php`, 마이그레이션, 모델, 서비스, 커맨드 +**환경별 수동 설정:** `.env` (STAT_DB_*), DB 생성, cron + +### 0.8 핵심 코딩 규칙 (이 작업에 적용) + +1. **Service-First**: 비즈니스 로직 → Service, Controller는 DI + 호출만 +2. **FormRequest**: Controller에서 직접 검증 금지 +3. **BelongsToTenant**: 원본 모델만 적용, 통계 모델은 tenant_id WHERE 직접 사용 +4. **i18n**: 메시지는 `__('message.xxx')` 형태 +5. **ApiResponse**: `use App\Helpers\ApiResponse;` → `ApiResponse::handle()` +6. **Swagger**: 별도 파일 `api/app/Swagger/v1/{Resource}Api.php`에 작성 +7. **커밋**: 사용자 승인 후에만 커밋 (자동 커밋 금지) + +### 0.9 작업 시작 체크리스트 + +``` +새 세션에서 이 문서를 받았을 때: + +□ 1. 이 문서의 "📍 현재 진행 상태" 확인 +□ 2. Phase별 작업 상태 (⏳/🔄/✅) 확인 +□ 3. Docker 실행 확인: docker compose ps (docker/ 디렉토리) +□ 4. DB 접속 확인: docker compose exec mysql mysql -u root -proot samdb +□ 5. sam_stat DB 존재 여부 확인: SHOW DATABASES LIKE 'sam_stat'; +□ 6. 마이그레이션 상태 확인: cd api && php artisan migrate:status +□ 7. 다음 작업 항목의 "비고" 컬럼 참조하여 작업 시작 +``` + +--- + +## 1. 개요 + +### 1.1 배경 + +SAM ERP는 219개 테이블, 17개 비즈니스 도메인을 가진 종합 제조/건설 ERP 시스템이다. +현재 대시보드(DashboardService, ReportService 등)는 **원본 DB(samdb)에서 실시간 집계**하는 방식으로 동작한다. + +**문제점:** +- 원본 DB에 집계 쿼리 부하 (JOIN, GROUP BY, SUM 등) +- 과거 데이터 추세 분석 불가 (스냅샷 없음) +- 도메인별 KPI 누적 관리 불가 +- 대시보드 응답 속도 저하 가능성 +- 통계 요구사항 증가 시 원본 스키마 오염 + +**해결 방안:** +- `sam_stat` 별도 DB에 사전 집계(pre-aggregated) 통계 데이터 저장 +- 배치/스케줄러로 원본(samdb) → 통계(sam_stat) DB 동기화 +- 원본 DB 부하 분리, 빠른 조회, 이력 보존 + +### 1.2 설계 원칙 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 🎯 핵심 원칙 │ +├─────────────────────────────────────────────────────────────────┤ +│ 1. 원본 DB 무간섭 - sam_stat은 읽기 전용 파생 데이터 │ +│ 2. 멀티테넌트 유지 - 모든 통계 테이블에 tenant_id 필수 │ +│ 3. 시간축 기반 - 일/주/월/분기/년 단위 집계 지원 │ +│ 4. 확장 가능 - 새 도메인 통계 추가 시 테이블만 추가 │ +│ 5. 멱등성 보장 - 같은 기간 재집계 시 동일 결과 (UPSERT) │ +│ 6. 메타데이터 드리븐 - stat_definitions로 동적 통계 정의 가능 │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### 1.3 변경 승인 정책 + +| 분류 | 예시 | 승인 | +|------|------|------| +| ✅ 즉시 가능 | 통계 필드 추가, 집계 주기 변경, 문서 수정 | 불필요 | +| ⚠️ 컨펌 필요 | 새 통계 테이블 생성, 스케줄러 추가, 마이그레이션 | **필수** | +| 🔴 금지 | 원본 DB 스키마 변경, 원본 테이블에 통계 컬럼 추가 | 별도 협의 | + +--- + +## 2. 분석: 필요한 통계 도메인 + +SAM의 17개 비즈니스 도메인을 분석하여 8개 핵심 통계 영역을 도출했다. + +### 2.1 통계 도메인 매핑 + +| # | 통계 도메인 | 원본 테이블 | 핵심 지표 | 우선순위 | +|---|-----------|-----------|----------|---------| +| 1 | **매출/수주** | orders, order_items, sales, clients | 수주액, 매출액, 수주건수, 고객별 매출 | 🔴 P0 | +| 2 | **재무/회계** | deposits, withdrawals, purchases, bills, bank_transactions | 입출금, 미수/미지급, 자금흐름, 어음현황 | 🔴 P0 | +| 3 | **생산/작업** | work_orders, work_order_items, work_results | 생산량, 작업효율, 불량률, 납기준수율 | 🔴 P0 | +| 4 | **재고/자재** | stocks, stock_transactions, material_receipts, shipments | 재고회전율, 입출고량, 안전재고, 로트추적 | 🟡 P1 | +| 5 | **견적/영업** | quotes, quote_items, sales_prospects, biddings | 수주전환율, 견적성공률, 영업파이프라인 | 🟡 P1 | +| 6 | **인사/근태** | attendance, leaves, payrolls, salaries | 출근율, 근태현황, 인건비, 부서별통계 | 🟡 P1 | +| 7 | **건설/프로젝트** | sites, contracts, expected_expenses, labor_distributions | 프로젝트수익률, 공정진행률, 원가분석 | 🟢 P2 | +| 8 | **시스템/감사** | audit_logs, api_request_logs, fcm_send_logs | API사용량, 사용자활동, 알림발송률 | 🟢 P2 | + +--- + +## 3. sam_stat 데이터베이스 설계 + +### 3.1 아키텍처 개요 + +``` +┌──────────────────────────────────────────────────────────────────┐ +│ sam_stat DB │ +├──────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────┐ ┌─────────────────────────────────┐ │ +│ │ 메타 테이블 (2) │ │ 이벤트/팩트 테이블 (2) │ │ +│ │ │ │ │ │ +│ │ stat_definitions │ │ stat_events │ │ +│ │ stat_job_logs │ │ stat_snapshots │ │ +│ └─────────────────────┘ └─────────────────────────────────┘ │ +│ │ +│ ┌───────────────────────────────────────────────────────────┐ │ +│ │ 도메인별 집계 테이블 (8 도메인) │ │ +│ │ │ │ +│ │ stat_sales_daily stat_inventory_daily │ │ +│ │ stat_finance_daily stat_quote_pipeline_daily │ │ +│ │ stat_production_daily stat_hr_attendance_daily │ │ +│ │ stat_project_monthly stat_system_daily │ │ +│ │ │ │ +│ │ 요약 테이블 (월간/연간) │ │ +│ │ │ │ +│ │ stat_sales_monthly stat_finance_monthly │ │ +│ │ stat_production_monthly stat_kpi_monthly │ │ +│ │ │ │ +│ └───────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────┐ ┌─────────────────────────────────┐ │ +│ │ 차원 테이블 (Dim) │ │ KPI/알림 테이블 │ │ +│ │ │ │ │ │ +│ │ dim_date │ │ stat_kpi_targets │ │ +│ │ dim_client │ │ stat_alerts │ │ +│ │ dim_product │ │ │ │ +│ └─────────────────────┘ └─────────────────────────────────┘ │ +│ │ +│ 총 테이블: 18개 │ +└──────────────────────────────────────────────────────────────────┘ +``` + +### 3.2 데이터 흐름 + +``` +samdb (원본) sam_stat (통계) +┌──────────┐ ┌──────────────┐ +│ orders │──┐ │ │ +│ sales │──┤ Scheduler │ stat_sales_ │ +│ deposits │──┼──(매일 02:00)──→│ daily │ +│ stocks │──┤ │ │ +│ work_ │──┤ │ stat_finance_│ +│ orders │──┘ │ daily │ +│ │ │ │ +│ │ Scheduler │ stat_*_ │ +│ │──(매월 1일)──────→│ monthly │ +│ │ │ │ +│ │ 실시간 이벤트 │ stat_events │ +│ │──(Observer)─────→│ │ +└──────────┘ └──────────────┘ +``` + +--- + +## 4. 테이블 상세 설계 + +### 4.1 메타 테이블 + +#### `stat_definitions` - 통계 정의 (메타데이터 드리븐) + +```sql +CREATE TABLE stat_definitions ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + code VARCHAR(100) NOT NULL UNIQUE, -- 'sales_daily_revenue' + domain VARCHAR(50) NOT NULL, -- 'sales', 'finance', 'production' + name VARCHAR(200) NOT NULL, -- '일일 매출액' + description TEXT NULL, + source_tables JSON NOT NULL, -- ["orders", "order_items", "sales"] + aggregation VARCHAR(20) NOT NULL DEFAULT 'daily', -- daily, weekly, monthly, quarterly, yearly + query_template TEXT NULL, -- 집계 SQL 템플릿 (선택) + is_active BOOLEAN NOT NULL DEFAULT TRUE, + config JSON NULL, -- 추가 설정 (임계값, 단위 등) + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + + INDEX idx_domain (domain), + INDEX idx_aggregation (aggregation), + INDEX idx_active (is_active) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +#### `stat_job_logs` - 집계 작업 이력 + +```sql +CREATE TABLE stat_job_logs ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + tenant_id BIGINT UNSIGNED NOT NULL, + job_type VARCHAR(100) NOT NULL, -- 'sales_daily', 'finance_monthly' + target_date DATE NOT NULL, -- 집계 대상 날짜 + status ENUM('pending','running','completed','failed') NOT NULL DEFAULT 'pending', + records_processed INT UNSIGNED DEFAULT 0, + error_message TEXT NULL, + started_at TIMESTAMP NULL, + completed_at TIMESTAMP NULL, + duration_ms INT UNSIGNED NULL, + created_at TIMESTAMP NULL, + + INDEX idx_tenant_job (tenant_id, job_type), + INDEX idx_status (status), + INDEX idx_target_date (target_date) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +--- + +### 4.2 차원 테이블 (Dimension) + +#### `dim_date` - 날짜 차원 + +```sql +CREATE TABLE dim_date ( + date_key DATE PRIMARY KEY, -- '2026-01-29' + year SMALLINT NOT NULL, + quarter TINYINT NOT NULL, -- 1~4 + month TINYINT NOT NULL, + week TINYINT NOT NULL, -- ISO week + day_of_week TINYINT NOT NULL, -- 1(월)~7(일) + day_of_month TINYINT NOT NULL, + is_weekend BOOLEAN NOT NULL, + is_holiday BOOLEAN NOT NULL DEFAULT FALSE, + holiday_name VARCHAR(100) NULL, + fiscal_year SMALLINT NULL, -- 회계연도 + fiscal_quarter TINYINT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +#### `dim_client` - 고객 차원 (스냅샷) + +```sql +CREATE TABLE dim_client ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + tenant_id BIGINT UNSIGNED NOT NULL, + client_id BIGINT UNSIGNED NOT NULL, -- 원본 clients.id + client_name VARCHAR(200) NOT NULL, + client_group_id BIGINT UNSIGNED NULL, + client_group_name VARCHAR(200) NULL, + client_type VARCHAR(50) NULL, -- 고객/공급업체/양쪽 + region VARCHAR(100) NULL, + valid_from DATE NOT NULL, + valid_to DATE NULL, -- NULL = 현재 유효 + is_current BOOLEAN NOT NULL DEFAULT TRUE, + + INDEX idx_tenant_client (tenant_id, client_id), + INDEX idx_current (is_current) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +#### `dim_product` - 제품 차원 (스냅샷) + +```sql +CREATE TABLE dim_product ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + tenant_id BIGINT UNSIGNED NOT NULL, + product_id BIGINT UNSIGNED NOT NULL, -- 원본 products.id + product_code VARCHAR(100) NOT NULL, + product_name VARCHAR(300) NOT NULL, + product_type VARCHAR(50) NULL, -- PRODUCT/PART/SUBASSEMBLY + category_id BIGINT UNSIGNED NULL, + category_name VARCHAR(200) NULL, + valid_from DATE NOT NULL, + valid_to DATE NULL, + is_current BOOLEAN NOT NULL DEFAULT TRUE, + + INDEX idx_tenant_product (tenant_id, product_id), + INDEX idx_current (is_current) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +--- + +### 4.3 도메인별 집계 테이블 (Fact) + +#### 🔴 P0: `stat_sales_daily` - 매출/수주 일일 통계 + +```sql +CREATE TABLE stat_sales_daily ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + tenant_id BIGINT UNSIGNED NOT NULL, + stat_date DATE NOT NULL, + + -- 수주 + order_count INT UNSIGNED DEFAULT 0, -- 신규 수주 건수 + order_amount DECIMAL(18,2) DEFAULT 0, -- 수주 금액 + order_item_count INT UNSIGNED DEFAULT 0, -- 수주 품목 수 + + -- 매출 + sales_count INT UNSIGNED DEFAULT 0, -- 매출 건수 + sales_amount DECIMAL(18,2) DEFAULT 0, -- 매출 금액 + sales_tax_amount DECIMAL(18,2) DEFAULT 0, -- 세액 + + -- 고객 + new_client_count INT UNSIGNED DEFAULT 0, -- 신규 고객 수 + active_client_count INT UNSIGNED DEFAULT 0, -- 활성 고객 수 + + -- 수주 상태별 건수 + order_draft_count INT UNSIGNED DEFAULT 0, + order_confirmed_count INT UNSIGNED DEFAULT 0, + order_in_progress_count INT UNSIGNED DEFAULT 0, + order_completed_count INT UNSIGNED DEFAULT 0, + order_cancelled_count INT UNSIGNED DEFAULT 0, + + -- 출하 + shipment_count INT UNSIGNED DEFAULT 0, + shipment_amount DECIMAL(18,2) DEFAULT 0, + + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + + UNIQUE KEY uk_tenant_date (tenant_id, stat_date), + INDEX idx_date (stat_date), + INDEX idx_tenant (tenant_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +#### 🔴 P0: `stat_finance_daily` - 재무 일일 통계 + +```sql +CREATE TABLE stat_finance_daily ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + tenant_id BIGINT UNSIGNED NOT NULL, + stat_date DATE NOT NULL, + + -- 입출금 + deposit_count INT UNSIGNED DEFAULT 0, + deposit_amount DECIMAL(18,2) DEFAULT 0, + withdrawal_count INT UNSIGNED DEFAULT 0, + withdrawal_amount DECIMAL(18,2) DEFAULT 0, + net_cashflow DECIMAL(18,2) DEFAULT 0, -- 입금 - 출금 + + -- 매입 + purchase_count INT UNSIGNED DEFAULT 0, + purchase_amount DECIMAL(18,2) DEFAULT 0, + purchase_tax_amount DECIMAL(18,2) DEFAULT 0, + + -- 미수/미지급 + receivable_balance DECIMAL(18,2) DEFAULT 0, -- 미수금 잔액 + payable_balance DECIMAL(18,2) DEFAULT 0, -- 미지급 잔액 + overdue_receivable DECIMAL(18,2) DEFAULT 0, -- 연체 미수금 + + -- 어음 + bill_issued_count INT UNSIGNED DEFAULT 0, + bill_issued_amount DECIMAL(18,2) DEFAULT 0, + bill_matured_count INT UNSIGNED DEFAULT 0, + bill_matured_amount DECIMAL(18,2) DEFAULT 0, + + -- 카드 + card_transaction_count INT UNSIGNED DEFAULT 0, + card_transaction_amount DECIMAL(18,2) DEFAULT 0, + + -- 은행 + bank_balance_total DECIMAL(18,2) DEFAULT 0, -- 전 계좌 잔액 합계 + + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + + UNIQUE KEY uk_tenant_date (tenant_id, stat_date), + INDEX idx_date (stat_date) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +#### 🔴 P0: `stat_production_daily` - 생산 일일 통계 + +```sql +CREATE TABLE stat_production_daily ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + tenant_id BIGINT UNSIGNED NOT NULL, + stat_date DATE NOT NULL, + + -- 작업지시 + wo_created_count INT UNSIGNED DEFAULT 0, -- 신규 작업지시 + wo_completed_count INT UNSIGNED DEFAULT 0, -- 완료 작업지시 + wo_in_progress_count INT UNSIGNED DEFAULT 0, -- 진행중 + wo_overdue_count INT UNSIGNED DEFAULT 0, -- 납기 초과 + + -- 생산량 + production_qty DECIMAL(18,2) DEFAULT 0, -- 생산 수량 + defect_qty DECIMAL(18,2) DEFAULT 0, -- 불량 수량 + defect_rate DECIMAL(5,2) DEFAULT 0, -- 불량률 (%) + + -- 작업 효율 + planned_hours DECIMAL(10,2) DEFAULT 0, -- 계획 공수 + actual_hours DECIMAL(10,2) DEFAULT 0, -- 실적 공수 + efficiency_rate DECIMAL(5,2) DEFAULT 0, -- 효율 (%) + + -- 작업자 + active_worker_count INT UNSIGNED DEFAULT 0, + issue_count INT UNSIGNED DEFAULT 0, -- 발생 이슈 수 + + -- 납기 + on_time_delivery_count INT UNSIGNED DEFAULT 0, + late_delivery_count INT UNSIGNED DEFAULT 0, + delivery_rate DECIMAL(5,2) DEFAULT 0, -- 납기준수율 (%) + + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + + UNIQUE KEY uk_tenant_date (tenant_id, stat_date), + INDEX idx_date (stat_date) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +#### 🟡 P1: `stat_inventory_daily` - 재고 일일 통계 + +```sql +CREATE TABLE stat_inventory_daily ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + tenant_id BIGINT UNSIGNED NOT NULL, + stat_date DATE NOT NULL, + + -- 재고 현황 + total_sku_count INT UNSIGNED DEFAULT 0, -- 총 SKU 수 + total_stock_qty DECIMAL(18,2) DEFAULT 0, -- 총 재고 수량 + total_stock_value DECIMAL(18,2) DEFAULT 0, -- 총 재고 금액 + + -- 입출고 + receipt_count INT UNSIGNED DEFAULT 0, -- 입고 건수 + receipt_qty DECIMAL(18,2) DEFAULT 0, + receipt_amount DECIMAL(18,2) DEFAULT 0, + issue_count INT UNSIGNED DEFAULT 0, -- 출고 건수 + issue_qty DECIMAL(18,2) DEFAULT 0, + issue_amount DECIMAL(18,2) DEFAULT 0, + + -- 안전재고 + below_safety_count INT UNSIGNED DEFAULT 0, -- 안전재고 미달 품목 수 + zero_stock_count INT UNSIGNED DEFAULT 0, -- 재고 0 품목 수 + excess_stock_count INT UNSIGNED DEFAULT 0, -- 과잉 재고 품목 수 + + -- 품질검사 + inspection_count INT UNSIGNED DEFAULT 0, + inspection_pass_count INT UNSIGNED DEFAULT 0, + inspection_fail_count INT UNSIGNED DEFAULT 0, + inspection_pass_rate DECIMAL(5,2) DEFAULT 0, -- 합격률 (%) + + -- 재고회전 + turnover_rate DECIMAL(8,2) DEFAULT 0, -- 재고회전율 + + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + + UNIQUE KEY uk_tenant_date (tenant_id, stat_date), + INDEX idx_date (stat_date) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +#### 🟡 P1: `stat_quote_pipeline_daily` - 견적/영업 일일 통계 + +```sql +CREATE TABLE stat_quote_pipeline_daily ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + tenant_id BIGINT UNSIGNED NOT NULL, + stat_date DATE NOT NULL, + + -- 견적 + quote_created_count INT UNSIGNED DEFAULT 0, + quote_amount DECIMAL(18,2) DEFAULT 0, + quote_approved_count INT UNSIGNED DEFAULT 0, + quote_rejected_count INT UNSIGNED DEFAULT 0, + quote_conversion_count INT UNSIGNED DEFAULT 0, -- 수주 전환 건수 + quote_conversion_rate DECIMAL(5,2) DEFAULT 0, -- 전환율 (%) + + -- 영업 기회 + prospect_created_count INT UNSIGNED DEFAULT 0, + prospect_won_count INT UNSIGNED DEFAULT 0, + prospect_lost_count INT UNSIGNED DEFAULT 0, + prospect_amount DECIMAL(18,2) DEFAULT 0, -- 파이프라인 금액 + + -- 입찰 + bidding_count INT UNSIGNED DEFAULT 0, + bidding_won_count INT UNSIGNED DEFAULT 0, + bidding_amount DECIMAL(18,2) DEFAULT 0, + + -- 상담 + consultation_count INT UNSIGNED DEFAULT 0, + + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + + UNIQUE KEY uk_tenant_date (tenant_id, stat_date), + INDEX idx_date (stat_date) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +#### 🟡 P1: `stat_hr_attendance_daily` - 인사/근태 일일 통계 + +```sql +CREATE TABLE stat_hr_attendance_daily ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + tenant_id BIGINT UNSIGNED NOT NULL, + stat_date DATE NOT NULL, + + -- 근태 + total_employees INT UNSIGNED DEFAULT 0, -- 전체 직원 수 + attendance_count INT UNSIGNED DEFAULT 0, -- 출근 인원 + late_count INT UNSIGNED DEFAULT 0, -- 지각 + absent_count INT UNSIGNED DEFAULT 0, -- 결근 + attendance_rate DECIMAL(5,2) DEFAULT 0, -- 출근율 (%) + + -- 휴가 + leave_count INT UNSIGNED DEFAULT 0, -- 휴가 사용 + leave_annual_count INT UNSIGNED DEFAULT 0, -- 연차 + leave_sick_count INT UNSIGNED DEFAULT 0, -- 병가 + leave_other_count INT UNSIGNED DEFAULT 0, -- 기타 + + -- 초과근무 + overtime_hours DECIMAL(10,2) DEFAULT 0, + overtime_employee_count INT UNSIGNED DEFAULT 0, + + -- 인건비 (급여 정산 기준) + total_labor_cost DECIMAL(18,2) DEFAULT 0, + + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + + UNIQUE KEY uk_tenant_date (tenant_id, stat_date), + INDEX idx_date (stat_date) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +#### 🟢 P2: `stat_project_monthly` - 건설/프로젝트 월간 통계 + +```sql +CREATE TABLE stat_project_monthly ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + tenant_id BIGINT UNSIGNED NOT NULL, + stat_year SMALLINT NOT NULL, + stat_month TINYINT NOT NULL, + + -- 프로젝트 현황 + active_site_count INT UNSIGNED DEFAULT 0, + completed_site_count INT UNSIGNED DEFAULT 0, + new_contract_count INT UNSIGNED DEFAULT 0, + contract_total_amount DECIMAL(18,2) DEFAULT 0, + + -- 원가 + expected_expense_total DECIMAL(18,2) DEFAULT 0, + actual_expense_total DECIMAL(18,2) DEFAULT 0, + labor_cost_total DECIMAL(18,2) DEFAULT 0, + material_cost_total DECIMAL(18,2) DEFAULT 0, + + -- 수익률 + gross_profit DECIMAL(18,2) DEFAULT 0, + gross_profit_rate DECIMAL(5,2) DEFAULT 0, -- 수익률 (%) + + -- 이슈 + handover_report_count INT UNSIGNED DEFAULT 0, + structure_review_count INT UNSIGNED DEFAULT 0, + + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + + UNIQUE KEY uk_tenant_year_month (tenant_id, stat_year, stat_month), + INDEX idx_year_month (stat_year, stat_month) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +#### 🟢 P2: `stat_system_daily` - 시스템 일일 통계 + +```sql +CREATE TABLE stat_system_daily ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + tenant_id BIGINT UNSIGNED NOT NULL, + stat_date DATE NOT NULL, + + -- API 사용량 + api_request_count INT UNSIGNED DEFAULT 0, + api_error_count INT UNSIGNED DEFAULT 0, + api_avg_response_ms INT UNSIGNED DEFAULT 0, + + -- 사용자 활동 + active_user_count INT UNSIGNED DEFAULT 0, + login_count INT UNSIGNED DEFAULT 0, + + -- 감사 + audit_create_count INT UNSIGNED DEFAULT 0, + audit_update_count INT UNSIGNED DEFAULT 0, + audit_delete_count INT UNSIGNED DEFAULT 0, + + -- 알림 + fcm_sent_count INT UNSIGNED DEFAULT 0, + fcm_failed_count INT UNSIGNED DEFAULT 0, + + -- 파일 + file_upload_count INT UNSIGNED DEFAULT 0, + file_upload_size_mb DECIMAL(10,2) DEFAULT 0, + + -- 결재 + approval_submitted_count INT UNSIGNED DEFAULT 0, + approval_completed_count INT UNSIGNED DEFAULT 0, + approval_avg_hours DECIMAL(8,2) DEFAULT 0, -- 평균 처리 시간 + + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + + UNIQUE KEY uk_tenant_date (tenant_id, stat_date), + INDEX idx_date (stat_date) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +--- + +### 4.4 월간 요약 테이블 + +#### `stat_sales_monthly` - 매출 월간 요약 + +```sql +CREATE TABLE stat_sales_monthly ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + tenant_id BIGINT UNSIGNED NOT NULL, + stat_year SMALLINT NOT NULL, + stat_month TINYINT NOT NULL, + + -- 일일 합산 + order_count INT UNSIGNED DEFAULT 0, + order_amount DECIMAL(18,2) DEFAULT 0, + sales_count INT UNSIGNED DEFAULT 0, + sales_amount DECIMAL(18,2) DEFAULT 0, + shipment_count INT UNSIGNED DEFAULT 0, + shipment_amount DECIMAL(18,2) DEFAULT 0, + + -- 월간 고유 지표 + unique_client_count INT UNSIGNED DEFAULT 0, -- 거래 고객 수 + avg_order_amount DECIMAL(18,2) DEFAULT 0, -- 평균 수주 금액 + top_client_id BIGINT UNSIGNED NULL, -- 최다 거래 고객 + top_client_amount DECIMAL(18,2) DEFAULT 0, + mom_growth_rate DECIMAL(8,2) NULL, -- 전월 대비 성장률 (%) + yoy_growth_rate DECIMAL(8,2) NULL, -- 전년동월 대비 (%) + + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + + UNIQUE KEY uk_tenant_year_month (tenant_id, stat_year, stat_month), + INDEX idx_year_month (stat_year, stat_month) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +#### `stat_finance_monthly` - 재무 월간 요약 + +```sql +CREATE TABLE stat_finance_monthly ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + tenant_id BIGINT UNSIGNED NOT NULL, + stat_year SMALLINT NOT NULL, + stat_month TINYINT NOT NULL, + + deposit_total DECIMAL(18,2) DEFAULT 0, + withdrawal_total DECIMAL(18,2) DEFAULT 0, + net_cashflow DECIMAL(18,2) DEFAULT 0, + purchase_total DECIMAL(18,2) DEFAULT 0, + card_total DECIMAL(18,2) DEFAULT 0, + + receivable_end DECIMAL(18,2) DEFAULT 0, -- 월말 미수금 + payable_end DECIMAL(18,2) DEFAULT 0, -- 월말 미지급 + bank_balance_end DECIMAL(18,2) DEFAULT 0, -- 월말 잔액 + + mom_cashflow_change DECIMAL(8,2) NULL, -- 전월 대비 현금흐름 변화 (%) + + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + + UNIQUE KEY uk_tenant_year_month (tenant_id, stat_year, stat_month), + INDEX idx_year_month (stat_year, stat_month) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +#### `stat_production_monthly` - 생산 월간 요약 + +```sql +CREATE TABLE stat_production_monthly ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + tenant_id BIGINT UNSIGNED NOT NULL, + stat_year SMALLINT NOT NULL, + stat_month TINYINT NOT NULL, + + wo_total_count INT UNSIGNED DEFAULT 0, + wo_completed_count INT UNSIGNED DEFAULT 0, + production_qty DECIMAL(18,2) DEFAULT 0, + defect_qty DECIMAL(18,2) DEFAULT 0, + avg_defect_rate DECIMAL(5,2) DEFAULT 0, + avg_efficiency_rate DECIMAL(5,2) DEFAULT 0, + avg_delivery_rate DECIMAL(5,2) DEFAULT 0, + total_planned_hours DECIMAL(10,2) DEFAULT 0, + total_actual_hours DECIMAL(10,2) DEFAULT 0, + issue_total_count INT UNSIGNED DEFAULT 0, + + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + + UNIQUE KEY uk_tenant_year_month (tenant_id, stat_year, stat_month), + INDEX idx_year_month (stat_year, stat_month) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +--- + +### 4.5 KPI/알림 테이블 + +#### `stat_kpi_targets` - KPI 목표 설정 + +```sql +CREATE TABLE stat_kpi_targets ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + tenant_id BIGINT UNSIGNED NOT NULL, + stat_year SMALLINT NOT NULL, + stat_month TINYINT NULL, -- NULL = 연간 목표 + + domain VARCHAR(50) NOT NULL, -- 'sales', 'production' + metric_code VARCHAR(100) NOT NULL, -- 'monthly_sales_amount' + target_value DECIMAL(18,2) NOT NULL, + unit VARCHAR(20) NOT NULL DEFAULT 'KRW', -- KRW, %, count, hours + description VARCHAR(300) NULL, + + created_by BIGINT UNSIGNED NULL, + created_at TIMESTAMP NULL, + updated_at TIMESTAMP NULL, + + UNIQUE KEY uk_tenant_metric (tenant_id, stat_year, stat_month, metric_code), + INDEX idx_domain (domain) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +#### `stat_alerts` - 통계 기반 알림 + +```sql +CREATE TABLE stat_alerts ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + tenant_id BIGINT UNSIGNED NOT NULL, + domain VARCHAR(50) NOT NULL, + alert_type VARCHAR(100) NOT NULL, -- 'below_target', 'anomaly', 'threshold' + severity ENUM('info','warning','critical') NOT NULL DEFAULT 'info', + title VARCHAR(300) NOT NULL, + message TEXT NOT NULL, + metric_code VARCHAR(100) NULL, + current_value DECIMAL(18,2) NULL, + threshold_value DECIMAL(18,2) NULL, + is_read BOOLEAN NOT NULL DEFAULT FALSE, + is_resolved BOOLEAN NOT NULL DEFAULT FALSE, + resolved_at TIMESTAMP NULL, + resolved_by BIGINT UNSIGNED NULL, + created_at TIMESTAMP NULL, + + INDEX idx_tenant_unread (tenant_id, is_read), + INDEX idx_severity (severity), + INDEX idx_domain (domain) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +--- + +### 4.6 이벤트/스냅샷 테이블 + +#### `stat_events` - 실시간 이벤트 로그 (확장용) + +```sql +CREATE TABLE stat_events ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + tenant_id BIGINT UNSIGNED NOT NULL, + domain VARCHAR(50) NOT NULL, + event_type VARCHAR(100) NOT NULL, -- 'order_created', 'payment_received' + entity_type VARCHAR(100) NOT NULL, -- 'Order', 'Deposit' + entity_id BIGINT UNSIGNED NOT NULL, + payload JSON NULL, -- 이벤트 데이터 + occurred_at TIMESTAMP NOT NULL, + + INDEX idx_tenant_domain (tenant_id, domain), + INDEX idx_occurred (occurred_at), + INDEX idx_entity (entity_type, entity_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +#### `stat_snapshots` - 상태 스냅샷 (특정 시점 전체 상태) + +```sql +CREATE TABLE stat_snapshots ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + tenant_id BIGINT UNSIGNED NOT NULL, + snapshot_date DATE NOT NULL, + domain VARCHAR(50) NOT NULL, + snapshot_type VARCHAR(50) NOT NULL DEFAULT 'daily', -- daily, weekly, monthly + data JSON NOT NULL, -- 전체 스냅샷 데이터 + created_at TIMESTAMP NULL, + + UNIQUE KEY uk_tenant_date_domain (tenant_id, snapshot_date, domain, snapshot_type), + INDEX idx_date (snapshot_date) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +--- + +## 5. 테이블 요약 + +| # | 테이블명 | 유형 | 도메인 | 집계 주기 | 우선순위 | +|---|---------|------|--------|----------|---------| +| 1 | `stat_definitions` | 메타 | 공통 | - | 🔴 P0 | +| 2 | `stat_job_logs` | 메타 | 공통 | - | 🔴 P0 | +| 3 | `dim_date` | 차원 | 공통 | 1회 생성 | 🔴 P0 | +| 4 | `dim_client` | 차원 | 공통 | SCD Type 2 | 🟡 P1 | +| 5 | `dim_product` | 차원 | 공통 | SCD Type 2 | 🟡 P1 | +| 6 | `stat_sales_daily` | 팩트 | 매출/수주 | 일간 | 🔴 P0 | +| 7 | `stat_finance_daily` | 팩트 | 재무/회계 | 일간 | 🔴 P0 | +| 8 | `stat_production_daily` | 팩트 | 생산/작업 | 일간 | 🔴 P0 | +| 9 | `stat_inventory_daily` | 팩트 | 재고/자재 | 일간 | 🟡 P1 | +| 10 | `stat_quote_pipeline_daily` | 팩트 | 견적/영업 | 일간 | 🟡 P1 | +| 11 | `stat_hr_attendance_daily` | 팩트 | 인사/근태 | 일간 | 🟡 P1 | +| 12 | `stat_project_monthly` | 팩트 | 건설/프로젝트 | 월간 | 🟢 P2 | +| 13 | `stat_system_daily` | 팩트 | 시스템/감사 | 일간 | 🟢 P2 | +| 14 | `stat_sales_monthly` | 요약 | 매출/수주 | 월간 | 🔴 P0 | +| 15 | `stat_finance_monthly` | 요약 | 재무/회계 | 월간 | 🔴 P0 | +| 16 | `stat_production_monthly` | 요약 | 생산/작업 | 월간 | 🔴 P0 | +| 17 | `stat_kpi_targets` | KPI | 공통 | 수동 설정 | 🟡 P1 | +| 18 | `stat_alerts` | 알림 | 공통 | 실시간 | 🟡 P1 | +| 19 | `stat_events` | 이벤트 | 공통 | 실시간 | 🟢 P2 | +| 20 | `stat_snapshots` | 스냅샷 | 공통 | 일/월 | 🟢 P2 | + +**총 20개 테이블** (메타 2 + 차원 3 + 일간팩트 6 + 월간팩트 1 + 월간요약 3 + KPI/알림 2 + 이벤트/스냅샷 2 + 시스템 1) + +--- + +## 6. 구현 계획 (Phase) + +### Phase 1: 인프라 구축 (P0) +| # | 작업 항목 | 상태 | 구체적 작업 내용 | +|---|----------|:----:|-----------------| +| 1.1 | sam_stat DB 생성 및 Laravel 연결 설정 | ✅ | ① Docker MySQL에 `CREATE DATABASE sam_stat` 실행 ② `api/config/database.php`에 `sam_stat` 연결 추가 ③ `api/.env`에 `STAT_DB_*` 환경변수 추가 | +| 1.2 | 메타 테이블 마이그레이션 | ✅ | `stat_definitions`, `stat_job_logs` 마이그레이션 생성 (`--database=sam_stat` 옵션) | +| 1.3 | dim_date 테이블 생성 및 시딩 | ✅ | 2020-01-01~2030-12-31 날짜 데이터 Seeder 작성 (4,018건) | +| 1.4 | 기반 모델 클래스 생성 | ✅ | `BaseStatModel`, `StatDefinition`, `StatJobLog`, `DimDate` 생성 | +| 1.5 | 집계 커맨드 기반 구조 | ✅ | `StatAggregateDailyCommand.php`, `StatAggregateMonthlyCommand.php` 생성 | +| 1.6 | StatAggregatorService 골격 | ✅ | `StatAggregatorService.php` + `StatDomainServiceInterface.php` - 테넌트 순회 + 도메인별 서비스 호출 구조 | + +**Phase 1 검증 방법:** +```bash +# DB 생성 확인 +docker compose exec mysql mysql -u root -proot -e "SHOW DATABASES LIKE 'sam_stat';" + +# 마이그레이션 실행 +cd api && php artisan migrate --database=sam_stat + +# dim_date 시딩 +cd api && php artisan db:seed --class=DimDateSeeder + +# 커맨드 확인 +cd api && php artisan stat:aggregate-daily --help +``` + +### Phase 2: P0 도메인 구축 +| # | 작업 항목 | 상태 | 구체적 작업 내용 | +|---|----------|:----:|-----------------| +| 2.1 | 매출 테이블 마이그레이션 | ⏳ | `stat_sales_daily` + `stat_sales_monthly` 마이그레이션 | +| 2.2 | 매출 모델 + 서비스 | ⏳ | `Models/Stats/Daily/StatSalesDaily.php`, `Monthly/StatSalesMonthly.php`, `Services/Stats/SalesStatService.php` - 원본: `orders`, `order_items`, `sales`, `clients`, `shipments` 조회 후 집계 | +| 2.3 | 재무 테이블 마이그레이션 | ⏳ | `stat_finance_daily` + `stat_finance_monthly` 마이그레이션 | +| 2.4 | 재무 모델 + 서비스 | ⏳ | `FinanceStatService.php` - 원본: `deposits`, `withdrawals`, `purchases`, `bills`, `bank_transactions`, `cards` 조회 후 집계 | +| 2.5 | 생산 테이블 마이그레이션 | ⏳ | `stat_production_daily` + `stat_production_monthly` 마이그레이션 | +| 2.6 | 생산 모델 + 서비스 | ⏳ | `ProductionStatService.php` - 원본: `work_orders`, `work_order_items`, `work_results`, `work_order_issues` 조회 후 집계 | +| 2.7 | 스케줄러 등록 | ⏳ | `api/routes/console.php`에 추가: `stat:aggregate-daily` (02:00), `stat:aggregate-monthly` (매월 1일 03:00) | + +**Phase 2 검증 방법:** +```bash +# 수동 집계 실행 (특정 날짜) +cd api && php artisan stat:aggregate-daily --date=2026-01-28 + +# 데이터 확인 +docker compose exec mysql mysql -u root -proot sam_stat \ + -e "SELECT * FROM stat_sales_daily WHERE stat_date='2026-01-28';" +``` + +### Phase 3: P1 도메인 확장 +| # | 작업 항목 | 상태 | 구체적 작업 내용 | +|---|----------|:----:|-----------------| +| 3.1 | 차원 테이블 | ⏳ | `dim_client`, `dim_product` 마이그레이션 + SCD Type 2 동기화 서비스 | +| 3.2 | 재고 통계 | ⏳ | `stat_inventory_daily` 마이그레이션 + `InventoryStatService.php` - 원본: `stocks`, `stock_transactions`, `material_receipts`, `shipments`, `inspections` | +| 3.3 | 견적/영업 통계 | ⏳ | `stat_quote_pipeline_daily` 마이그레이션 + `QuoteStatService.php` - 원본: `quotes`, `quote_items`, `sales_prospects`, `biddings` | +| 3.4 | 인사/근태 통계 | ⏳ | `stat_hr_attendance_daily` 마이그레이션 + `HrStatService.php` - 원본: `attendances`, `leaves`, `payrolls`, `salaries` | +| 3.5 | KPI/알림 | ⏳ | `stat_kpi_targets`, `stat_alerts` 마이그레이션 + `KpiAlertService.php` + 스케줄러 09:00 | + +### Phase 4: P2 도메인 + API + 대시보드 전환 +| # | 작업 항목 | 상태 | 구체적 작업 내용 | +|---|----------|:----:|-----------------| +| 4.1 | 건설/프로젝트 통계 | ⏳ | `stat_project_monthly` + `ProjectStatService.php` - 원본: `sites`, `contracts`, `expected_expenses` | +| 4.2 | 시스템 통계 | ⏳ | `stat_system_daily` + `SystemStatService.php` - 원본: `audit_logs`, `api_request_logs`, `fcm_send_logs` | +| 4.3 | 이벤트/스냅샷 | ⏳ | `stat_events`, `stat_snapshots` + Observer 패턴으로 실시간 이벤트 기록 | +| 4.4 | 통계 API | ⏳ | `StatController.php` + `api/routes/api/v1/stats.php` 라우트 파일 추가 + Swagger | +| 4.5 | 대시보드 전환 | ⏳ | `DashboardService.php` 내부 쿼리를 sam_stat 조회로 점진적 전환 (API 인터페이스 유지) | + +### Phase 5: 최적화 및 안정화 +| # | 작업 항목 | 상태 | 구체적 작업 내용 | +|---|----------|:----:|-----------------| +| 5.1 | 백필 스크립트 | ⏳ | `stat:backfill --from=2024-01-01 --to=2026-01-28` 커맨드 구현 | +| 5.2 | 정합성 검증 | ⏳ | `stat:verify --date=2026-01-28` 원본 vs 통계 교차 검증 커맨드 | +| 5.3 | 파티셔닝 | ⏳ | 일간 테이블 연도별 RANGE 파티셔닝 적용 (2년 이상 데이터 축적 후) | +| 5.4 | 캐싱 | ⏳ | 대시보드 조회 시 Redis 캐싱 (TTL 5분, 집계 완료 시 캐시 무효화) | +| 5.5 | 모니터링 | ⏳ | 집계 실패 시 stat_alerts + FCM 알림 연동 | + +--- + +## 7. 기술 설계 요약 + +### 7.1 Laravel 다중 DB 연결 + +```php +// config/database.php +'connections' => [ + 'mysql' => [ /* 기존 samdb */ ], + 'sam_stat' => [ + 'driver' => 'mysql', + 'host' => env('STAT_DB_HOST', '127.0.0.1'), + 'database' => env('STAT_DB_DATABASE', 'sam_stat'), + 'username' => env('STAT_DB_USERNAME', 'root'), + 'password' => env('STAT_DB_PASSWORD', ''), + // ... 나머지 동일 + ], +], +``` + +### 7.2 모델 구조 + +``` +api/app/Models/Stats/ +├── StatDefinition.php // connection = 'sam_stat' +├── StatJobLog.php +├── Dimensions/ +│ ├── DimDate.php +│ ├── DimClient.php +│ └── DimProduct.php +├── Daily/ +│ ├── StatSalesDaily.php +│ ├── StatFinanceDaily.php +│ ├── StatProductionDaily.php +│ ├── StatInventoryDaily.php +│ ├── StatQuotePipelineDaily.php +│ ├── StatHrAttendanceDaily.php +│ └── StatSystemDaily.php +├── Monthly/ +│ ├── StatSalesMonthly.php +│ ├── StatFinanceMonthly.php +│ ├── StatProductionMonthly.php +│ └── StatProjectMonthly.php +├── StatKpiTarget.php +├── StatAlert.php +├── StatEvent.php +└── StatSnapshot.php +``` + +### 7.3 서비스 구조 + +``` +api/app/Services/Stats/ +├── StatAggregatorService.php // 집계 오케스트레이터 +├── SalesStatService.php // 매출/수주 집계 +├── FinanceStatService.php // 재무 집계 +├── ProductionStatService.php // 생산 집계 +├── InventoryStatService.php // 재고 집계 +├── QuoteStatService.php // 견적/영업 집계 +├── HrStatService.php // 인사/근태 집계 +├── ProjectStatService.php // 건설 집계 +├── SystemStatService.php // 시스템 집계 +└── KpiAlertService.php // KPI 목표 대비 알림 +``` + +### 7.4 스케줄러 구조 + +```php +// app/Console/Kernel.php (또는 routes/console.php) + +// 일간 집계 - 매일 02:00 +Schedule::command('stat:aggregate-daily') + ->dailyAt('02:00') + ->withoutOverlapping(); + +// 월간 집계 - 매월 1일 03:00 +Schedule::command('stat:aggregate-monthly') + ->monthlyOn(1, '03:00') + ->withoutOverlapping(); + +// KPI 알림 체크 - 매일 09:00 +Schedule::command('stat:check-kpi-alerts') + ->dailyAt('09:00'); +``` + +### 7.5 집계 패턴 (UPSERT) + +```php +// 멱등성 보장: 같은 날짜 재실행 시 덮어쓰기 +StatSalesDaily::updateOrCreate( + ['tenant_id' => $tenantId, 'stat_date' => $date], + [ + 'order_count' => $orderCount, + 'order_amount' => $orderAmount, + // ... + ] +); +``` + +--- + +## 8. 참고 문서 + +| 문서 | 경로 | 용도 | +|------|------|------| +| DB 스키마 | `docs/specs/database-schema.md` | 원본 219개 테이블 구조 | +| 시스템 아키텍처 | `docs/architecture/system-overview.md` | 전체 시스템 구조, 미들웨어, Docker | +| API 규칙 | `docs/standards/api-rules.md` | Controller/Service 패턴, ApiResponse | +| 품질 체크리스트 | `docs/standards/quality-checklist.md` | 코드 품질 검증 항목 | +| 빠른 시작 | `docs/quickstart/quick-start.md` | 핵심 개발 규칙 3가지 | +| Swagger 가이드 | `docs/guides/swagger-guide.md` | Swagger 작성 규칙 (Phase 4.4 시) | +| Git 규칙 | `docs/standards/git-conventions.md` | 커밋 메시지 형식 | +| 프로젝트 CLAUDE.md | `/SAM/CLAUDE.md` | 프로젝트 전체 규칙 및 맥락 | +| API CLAUDE.md | `/SAM/api/CLAUDE.md` | API 저장소 상세 규칙 | + +--- + +## 9. 자기완결성 점검 결과 + +| # | 검증 항목 | 상태 | 비고 | +|---|----------|:----:|------| +| 1 | 작업 목적이 명확한가? | ✅ | 섹션 1.1: sam_stat 별도 DB로 통계 분리 | +| 2 | 성공 기준이 정의되어 있는가? | ✅ | 20개 테이블, 8 도메인, Phase별 검증 방법 명시 | +| 3 | 작업 범위가 구체적인가? | ✅ | 섹션 4: 테이블별 DDL, 섹션 6: Phase별 구체적 작업 | +| 4 | 의존성이 명시되어 있는가? | ✅ | 섹션 2.1: 원본 테이블 매핑, 섹션 0.2: DB 환경 | +| 5 | 참고 파일 경로가 정확한가? | ✅ | 섹션 0.1, 0.3: 실제 파일 경로 검증됨 (2026-01-29) | +| 6 | 단계별 절차가 실행 가능한가? | ✅ | Phase 1-5 구체적 작업 + bash 검증 커맨드 포함 | +| 7 | 검증 방법이 명시되어 있는가? | ✅ | Phase 1, 2에 검증 bash 커맨드 블록 포함 | +| 8 | 모호한 표현이 없는가? | ✅ | 파일 경로, 클래스명, 테이블명 모두 구체적 | + +### 새 세션 시뮬레이션 테스트 + +| 질문 | 답변 가능 | 참조 섹션 | +|------|:--------:|----------| +| Q1. 이 작업의 목적은 무엇인가? | ✅ | 1.1 배경 | +| Q2. 어디서부터 시작해야 하는가? | ✅ | 0.9 체크리스트 → 6. Phase 1 | +| Q3. 어떤 파일을 수정/생성해야 하는가? | ✅ | 0.1 프로젝트 구조 + 7.1~7.5 기술 설계 | +| Q4. 기존 코드에 어떤 영향이 있는가? | ✅ | 0.3 기존 대시보드/보고서 시스템 | +| Q5. DB 연결은 어떻게 설정하는가? | ✅ | 0.2 현재 DB 환경 + 7.1 Laravel 다중 DB | +| Q6. 코딩 규칙은 무엇인가? | ✅ | 0.8 핵심 코딩 규칙 | +| Q7. 작업 완료 확인 방법은? | ✅ | Phase 1, 2 검증 방법 블록 | +| Q8. 스케줄러는 어떻게 등록하는가? | ✅ | 0.4 기존 스케줄러 패턴 + 7.4 | +| Q9. Docker 환경은 어떻게 구성되어 있는가? | ✅ | 0.7 Docker 환경 | +| Q10. 막혔을 때 참고 문서는? | ✅ | 8. 참고 문서 (9개 문서 매핑) | + +**결과**: 10/10 통과 → ✅ 자기완결성 확보 + +--- + +## 10. 변경 이력 + +| 날짜 | 항목 | 내용 | +|------|------|------| +| 2026-01-29 | 초안 작성 | 프로젝트 분석 → 8개 도메인 도출 → 20개 테이블 설계 | +| 2026-01-29 | 자기완결성 보완 | 섹션 0 추가 (프로젝트 컨텍스트, DB 환경, 기존 시스템, 코딩 규칙, 체크리스트) | +| 2026-01-29 | 환경별 배포 구분 | 섹션 0.7 확장: 로컬(Docker) vs 개발서버(non-Docker) 구분, 배포 워크플로우 추가 | +| 2026-01-29 | Phase 1 완료 | 인프라 구축: sam_stat DB 생성, 메타/dim_date 마이그레이션, 기반 모델 4개, 커맨드 2개, AggregatorService + Interface | + +--- + +*이 문서는 /sc:plan 스킬로 생성되었습니다.* \ No newline at end of file