# SAM 통계 시스템 (sam_stat DB) 설계 계획

> **작성일**: 2026-01-29
> **목적**: SAM ERP의 확장 가능한 통계 전용 데이터베이스(sam_stat) 설계
> **기준 문서**: `docs/specs/database-schema.md`, `docs/architecture/system-overview.md`
> **상태**: ✅ 구현 완료

---

## 📍 현재 진행 상태

| 항목 | 내용 |
|------|------|
| **마지막 완료 작업** | Phase 6: 문서화 및 마무리 완료 (Swagger, DB 스키마 문서, 계획 문서 완료 처리) |
| **다음 작업** | ✅ 전체 완료 |
| **진행률** | 6/6 Phase (100%) |
| **마지막 업데이트** | 2026-01-30 |

---

## 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 | 매출 모델 + 서비스 | ✅ | `StatSalesDaily`, `StatSalesMonthly`, `SalesStatService` - orders, sales, clients, shipments 집계 |
| 2.3 | 재무 테이블 마이그레이션 | ✅ | `stat_finance_daily` + `stat_finance_monthly` 마이그레이션 |
| 2.4 | 재무 모델 + 서비스 | ✅ | `StatFinanceDaily`, `StatFinanceMonthly`, `FinanceStatService` - deposits, withdrawals, purchases, bills, bank_transactions 집계 |
| 2.5 | 생산 테이블 마이그레이션 | ✅ | `stat_production_daily` + `stat_production_monthly` 마이그레이션 |
| 2.6 | 생산 모델 + 서비스 | ✅ | `StatProductionDaily`, `StatProductionMonthly`, `ProductionStatService` - work_orders, work_results 집계 |
| 2.7 | 스케줄러 등록 | ✅ | `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` 마이그레이션 + 모델 + `DimensionSyncService` (SCD Type 2). 원본: `clients`→`dim_client`, `items`→`dim_product` (products 테이블 없어 items 사용) |
| 3.2 | 재고 통계 | ✅ | `stat_inventory_daily` 마이그레이션 + 모델 + `InventoryStatService` - 원본: `stocks`, `stock_transactions`, `inspections` |
| 3.3 | 견적/영업 통계 | ✅ | `stat_quote_pipeline_daily` 마이그레이션 + 모델 + `QuoteStatService` - 원본: `quotes`, `sales_prospects`, `biddings`, `sales_prospect_consultations` |
| 3.4 | 인사/근태 통계 | ✅ | `stat_hr_attendance_daily` 마이그레이션 + 모델 + `HrStatService` - 원본: `attendances`, `leaves`, `user_tenants` |
| 3.5 | KPI/알림 | ✅ | `stat_kpi_targets`, `stat_alerts` 마이그레이션 + 모델 + `KpiAlertService` + `StatCheckKpiAlertsCommand` + 스케줄러 09:00 |

### Phase 4: P2 도메인 + API + 대시보드 전환
| # | 작업 항목 | 상태 | 구체적 작업 내용 |
|---|----------|:----:|-----------------|
| 4.1 | 건설/프로젝트 통계 | ✅ | `stat_project_monthly` 마이그레이션 + 모델 + `ProjectStatService` - 원본: `sites`, `contracts`, `expected_expenses`. 월간 전용 도메인 |
| 4.2 | 시스템 통계 | ✅ | `stat_system_daily` 마이그레이션 + 모델 + `SystemStatService` - 원본: `api_request_logs`, `personal_access_tokens`(user_tenants 조인), `audit_logs`, `fcm_send_logs`, `files`, `approvals` |
| 4.3 | 이벤트/스냅샷 | ✅ | `stat_events`, `stat_snapshots` 마이그레이션 + 모델 + `StatEventService` + `StatEventObserver` (Order, Sale, Deposit, Withdrawal, Purchase, Approval에 등록) |
| 4.4 | 통계 API | ✅ | `StatController` (summary/daily/monthly/alerts) + `StatQueryService` + FormRequest 3개 + `routes/api/v1/stats.php`. Swagger는 Phase 5에서 추가 |
| 4.5 | 대시보드 전환 | ✅ | `DashboardService` getFinanceSummary/getSalesSummary → sam_stat 우선 조회 + 원본 DB 폴백. 응답에 `source` 필드 추가 |

### Phase 5: 최적화 및 안정화
| # | 작업 항목 | 상태 | 구체적 작업 내용 |
|---|----------|:----:|-----------------|
| 5.1 | 백필 스크립트 | ✅ | `StatBackfillCommand` - `stat:backfill --from= --to= --domain= --tenant= --skip-monthly --skip-dimensions`. CarbonPeriod 일간 순회 + 월간 집계 + 프로그레스바 + 에러 리포트. 테스트: 7도메인 0.2초 |
| 5.2 | 정합성 검증 | ✅ | `StatVerifyCommand` - `stat:verify --date= --tenant= --domain= --fix`. sales(수주건수/매출금액), finance(입금액/출금액), system(API요청수/감사로그수) 교차 검증. --fix 시 자동 재집계. 테스트: 6건 전부 일치 |
| 5.3 | 파티셔닝 준비 | ✅ | `2026_01_29_300001_prepare_partitioning_daily_tables.php` - 7개 일간 테이블 RANGE COLUMNS(stat_date) 파티셔닝. PK에 stat_date 포함, p2024~p2028 + p_future. 기존 파티션 여부 체크 후 스킵 |
| 5.4 | Redis 캐싱 | ✅ | `StatQueryService` - Cache::remember TTL 5분. 키 패턴: `stat:{daily\|monthly\|dashboard}:{tenantId}:...`. `invalidateCache()` 정적 메서드: Redis keys 패턴 매칭 삭제. 집계 완료 시 StatAggregatorService에서 자동 호출 |
| 5.5 | 모니터링 알림 | ✅ | `StatMonitorService` - recordAggregationFailure(critical), recordMissingData(warning), recordMismatch(critical), resolveAlerts(). StatAggregatorService catch 블록에서 자동 호출. stat_alerts 테이블 연동 검증 완료 |

### Phase 6: 문서화 및 마무리
| # | 작업 항목 | 상태 | 구체적 작업 내용 |
|---|----------|:----:|-----------------|
| 6.1 | Swagger API 문서 | ✅ | `app/Swagger/v1/StatApi.php` - Stats 태그, 4개 엔드포인트 (summary/daily/monthly/alerts), StatSalesDaily/StatFinanceDaily/StatDashboardSummary/StatAlert 스키마 정의. `l5-swagger:generate` 성공 |
| 6.2 | DB 스키마 문서 | ✅ | `docs/specs/database-schema.md`에 sam_stat 섹션 추가 - 20개 테이블 (메타 2, 차원 3, 일간 7, 월간 4, KPI/알림/이벤트 4) + Artisan 커맨드 5개 + API 엔드포인트 4개 |
| 6.3 | 계획 문서 완료 | ✅ | Phase 6 섹션 추가, 진행률 100%, 상태 완료 |

---

## 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 |
| 2026-01-29 | Phase 2 완료 | P0 도메인: 매출/재무/생산 일간+월간 테이블 6개, 모델 6개, 서비스 3개, 스케줄러 2개 등록. 실데이터 집계 검증 완료 |
| 2026-01-29 | Phase 3 완료 | P1 도메인: dim_client/dim_product 차원 + 재고/견적/인사 일간 3개 + KPI/알림 2개 = 테이블 7개, 모델 7개, 서비스 4개(Dimension/Inventory/Quote/Hr/KpiAlert), 커맨드 1개, 스케줄러 1개. 실데이터 검증 완료. products→items, client_groups.name→group_name 수정 |
| 2026-01-29 | Phase 4 완료 | P2 도메인 + API + 대시보드: stat_project_monthly/stat_system_daily/stat_events/stat_snapshots 테이블 4개, 모델 4개, 서비스 4개(Project/System/StatEvent/StatQuery), StatController + FormRequest 3개 + routes/stats.php, StatEventObserver(6모델), DashboardService sam_stat 전환(폴백 패턴). 버그: whereHas→DB Builder 제거, User모델경로 수정. sam_stat 총 20테이블 |
| 2026-01-29 | Phase 5 완료 | 최적화 및 안정화: StatBackfillCommand(백필), StatVerifyCommand(정합성 검증+자동 재집계), 파티셔닝 준비 마이그레이션(7테이블 RANGE), StatQueryService Redis 캐싱(TTL 5분+invalidateCache), StatMonitorService(집계 실패/누락/불일치 알림→stat_alerts), StatAggregatorService에 모니터링+캐시 무효화 연동. severity enum 수정(high→critical). 전체 테스트 통과 |
| 2026-01-30 | Phase 6 완료 | 문서화 및 마무리: StatApi.php Swagger 문서(4 엔드포인트, 4 스키마), database-schema.md sam_stat 섹션 추가(20테이블+5커맨드+4API). 전체 6 Phase 100% 완료 |

---

*이 문서는 /sc:plan 스킬로 생성되었습니다.*