Files
sam-docs/plans/archive/sam-stat-database-design-plan.md
권혁성 28b69e5449 docs: archive 37개 + COMPLETED 3개 복원 - 향후 docs/ 정식 문서화 시 참조용
- 완료 문서의 상세 내용은 추후 docs/ 구조화 시 정식 문서에 반영 예정
- HISTORY.md는 요약 인덱스로 유지, 개별 파일은 상세 참조용 보관

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 22:32:20 +09:00

58 KiB

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 기존 스케줄러 패턴 (따라야 할 패턴)

// 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 모델 패턴 (따라야 할 패턴)

// 기존 모델 예시 - 멀티테넌트 + 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)

# docker/docker-compose.yml 내 MySQL 서비스
# Docker 내부 호스트: sam-mysql-1
# sam_stat DB는 같은 MySQL 인스턴스에 생성 (별도 서버 불필요)
# 로컬 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회 세팅이 필요하다.

# 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 - 통계 정의 (메타데이터 드리븐)

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 - 집계 작업 이력

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 - 날짜 차원

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 - 고객 차원 (스냅샷)

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 - 제품 차원 (스냅샷)

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 - 매출/수주 일일 통계

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 - 재무 일일 통계

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 - 생산 일일 통계

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 - 재고 일일 통계

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 - 견적/영업 일일 통계

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 - 인사/근태 일일 통계

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 - 건설/프로젝트 월간 통계

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 - 시스템 일일 통계

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 - 매출 월간 요약

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 - 재무 월간 요약

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 - 생산 월간 요약

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 목표 설정

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 - 통계 기반 알림

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 - 실시간 이벤트 로그 (확장용)

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 - 상태 스냅샷 (특정 시점 전체 상태)

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.phpsam_stat 연결 추가 ③ api/.envSTAT_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 검증 방법:

# 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.phpstat:aggregate-daily (02:00), stat:aggregate-monthly (매월 1일 03:00) 등록

Phase 2 검증 방법:

# 수동 집계 실행 (특정 날짜)
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). 원본: clientsdim_client, itemsdim_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 연결

// 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 스케줄러 구조

// 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)

// 멱등성 보장: 같은 날짜 재실행 시 덮어쓰기
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 스킬로 생성되었습니다.