From 2bbf220dc87b46a7d14653cd9ba29e4ddeb03885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Sun, 1 Feb 2026 20:37:04 +0900 Subject: [PATCH] =?UTF-8?q?docs:=EA=B2=AC=EC=A0=81=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=EB=B6=84=EC=84=9D=20=EB=AC=B8=EC=84=9C=20=EB=B0=8F=20INDEX=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - INDEX.md 업데이트 - 견적관리 URL 마이그레이션 계획 수정 - API 분석 리포트, tenant-id 준수 계획 추가 - 견적관리 기능 문서 추가 Co-Authored-By: Claude Opus 4.5 --- INDEX.md | 2 + features/quotes/README.md | 455 +++++++++++++++++++ plans/api-analysis-report.md | 434 ++++++++++++++++++ plans/quote-management-url-migration-plan.md | 2 +- plans/tenant-id-compliance-plan.md | 410 +++++++++++++++++ 5 files changed, 1302 insertions(+), 1 deletion(-) create mode 100644 features/quotes/README.md create mode 100644 plans/api-analysis-report.md create mode 100644 plans/tenant-id-compliance-plan.md diff --git a/INDEX.md b/INDEX.md index 12c7783..2505615 100644 --- a/INDEX.md +++ b/INDEX.md @@ -19,6 +19,7 @@ | **품목관리** | `rules/item-policy.md` | 품목 정책 (유형, 예약어, API 규칙) | | **게시판** | `specs/board-system-spec.md` | 게시판 시스템 설계 | | **단가관리** | `rules/pricing-policy.md` | 원가/판매가 계산, 리비전 관리 | +| **견적관리** | `features/quotes/README.md` | 견적 시스템, BOM 계산, 10단계 로직 | | **MES 개발** | `projects/mes/README.md` | MES 프로젝트 개요 | --- @@ -124,6 +125,7 @@ docs/ | [boards/README.md](features/boards/README.md) | 게시판 시스템 구현 | | [boards/mng-implementation.md](features/boards/mng-implementation.md) | MNG 게시판 구현 상세 | | [hr/hr-api-analysis.md](features/hr/hr-api-analysis.md) | HR API 분석 (근태/직원/부서) | +| [quotes/README.md](features/quotes/README.md) | 견적 시스템 분석 (BOM 계산, 10단계 로직) | ### projects/ - 프로젝트별 문서 diff --git a/features/quotes/README.md b/features/quotes/README.md new file mode 100644 index 0000000..2db50e0 --- /dev/null +++ b/features/quotes/README.md @@ -0,0 +1,455 @@ +# 견적 시스템 분석 문서 + +> **목적**: 견적 시스템의 비즈니스 로직과 데이터 흐름을 이해하고 검증하기 위한 문서 + +## 목차 + +1. [개요](#1-개요) +2. [데이터베이스 구조](#2-데이터베이스-구조) +3. [견적 생성 흐름](#3-견적-생성-흐름) +4. [BOM 계산 로직 (10단계)](#4-bom-계산-로직-10단계) +5. [경동기업 전용 로직](#5-경동기업-전용-로직) +6. [상태 관리](#6-상태-관리) +7. [금액 계산 방식](#7-금액-계산-방식) +8. [관련 파일 목록](#8-관련-파일-목록) + +--- + +## 1. 개요 + +### 1.1 견적 유형 + +| 유형 | 코드 | 설명 | +|------|------|------| +| 제조 견적 | `manufacturing` | 스크린/철재 제품 제조 견적 | +| 시공 견적 | `construction` | 현장설명회 기반 시공 견적 | + +### 1.2 제품 카테고리 + +| 카테고리 | 코드 | 설명 | +|----------|------|------| +| 스크린 | `SCREEN` | 방화 스크린 제품 | +| 철재 | `STEEL` | 철재 제품 | + +### 1.3 핵심 서비스 클래스 + +``` +QuoteService ← 견적 CRUD, 상태 관리, 수주 전환 +├── QuoteNumberService ← 견적번호 생성 (KD-SC-YYMMDD-NN) +├── QuoteCalculationService ← 자동산출 실행, BOM 계산 호출 +└── FormulaEvaluatorService ← 수식 평가, 10단계 BOM 계산 + └── KyungdongFormulaHandler ← 경동기업(tenant_id=287) 전용 계산 +``` + +--- + +## 2. 데이터베이스 구조 + +### 2.1 테이블 관계도 + +``` +┌─────────────────┐ +│ quotes │ ← 견적 마스터 +├─────────────────┤ +│ id │ +│ tenant_id │──→ tenants +│ quote_number │ +│ quote_type │ ← manufacturing | construction +│ status │ ← pending→draft→finalized→converted +│ client_id │──→ clients +│ item_id │──→ items (완제품) +│ site_briefing_id│──→ site_briefings (시공 견적용) +│ order_id │──→ orders (수주 전환 후) +│ calculation_inputs (JSON) │ ← 자동산출 입력값 + BOM 결과 +│ options (JSON) │ ← 세부산출, 비용항목, 할인 정보 +└────────┬────────┘ + │ 1:N + ▼ +┌─────────────────┐ +│ quote_items │ ← 견적 품목 상세 +├─────────────────┤ +│ id │ +│ quote_id │──→ quotes +│ tenant_id │ +│ item_id │──→ items +│ item_code │ +│ item_name │ +│ calculated_quantity │ +│ unit_price │ +│ total_price │ +│ formula │ ← 수량 계산 수식 +│ formula_category│ ← 카테고리 (material/labor/install) +└─────────────────┘ + +┌─────────────────┐ +│ quote_revisions │ ← 수정 이력 +├─────────────────┤ +│ quote_id │──→ quotes +│ revision_number │ +│ previous_data (JSON) │ ← 수정 전 스냅샷 +└─────────────────┘ +``` + +### 2.2 quotes 테이블 주요 필드 + +| 필드명 | 타입 | 설명 | +|--------|------|------| +| `quote_number` | VARCHAR(50) | 견적번호 (예: KD-SC-251204-01) | +| `quote_type` | ENUM | `manufacturing` / `construction` | +| `status` | ENUM | 상태 (pending→draft→finalized→converted) | +| `product_category` | ENUM | `SCREEN` / `STEEL` | +| `open_size_width` | INT | 개구부 폭 (mm) | +| `open_size_height` | INT | 개구부 높이 (mm) | +| `quantity` | INT | 수량 | +| `material_cost` | DECIMAL | 재료비 합계 | +| `labor_cost` | DECIMAL | 노무비 | +| `install_cost` | DECIMAL | 설치비 | +| `subtotal` | DECIMAL | 소계 | +| `discount_rate` | DECIMAL | 할인율 (%) | +| `total_amount` | DECIMAL | 최종 금액 | +| `calculation_inputs` | JSON | 자동산출 입력값 및 BOM 결과 저장 | +| `options` | JSON | 세부산출항목, 비용항목, 할인정보 | +| `is_final` | BOOLEAN | 최종확정 여부 | + +### 2.3 calculation_inputs JSON 구조 + +```json +{ + "items": [ + { + "floor": "B1", + "code": "A-01", + "openWidth": 3000, + "openHeight": 2500, + "quantity": 2, + "productCategory": "SCREEN", + "productName": "KD-SCREEN-001", + "guideRailType": "wall", + "motorPower": "single" + } + ], + "bomResults": [ + { + "index": 0, + "finished_goods_code": "KD-SCREEN-001", + "items": [ + { + "item_code": "GUIDE-001", + "item_name": "가이드레일", + "quantity": 2.5, + "unit_price": 50000, + "total_price": 125000, + "is_manual": false + } + ], + "grand_total": 1250000 + } + ] +} +``` + +--- + +## 3. 견적 생성 흐름 + +### 3.1 제조 견적 생성 흐름 + +``` +[프론트엔드 - React] + │ + ▼ +1. 기본정보 입력 (거래처, 현장, 제품카테고리) + │ + ▼ +2. 위치별 규격 입력 (층/부호, 개구부 크기, 수량) + │ + ▼ +3. "견적 산출" 버튼 클릭 + │ + ▼ +[API 호출: POST /api/v1/quotes/bom/calculate-bulk] + │ + ▼ +4. QuoteCalculationService.calculateBomBulk() + │ + ├─→ 경동기업(287)? → KyungdongFormulaHandler + │ + └─→ 기타 테넌트 → 표준 BOM 계산 + │ + ▼ +5. 10단계 계산 결과 반환 + │ + ▼ +6. 프론트엔드에서 결과 표시 (세부산출내역) + │ + ▼ +7. "저장" 또는 "최종확정" 버튼 + │ + ▼ +[API 호출: POST /api/v1/quotes 또는 PUT /api/v1/quotes/{id}] + │ + ▼ +8. QuoteService.store() / update() + - quotes 테이블에 마스터 정보 저장 + - quote_items 테이블에 품목 상세 저장 + - calculation_inputs에 입력값 + BOM 결과 저장 +``` + +### 3.2 시공 견적 생성 흐름 (현장설명회 연계) + +``` +[현장설명회] + │ + ▼ +1. 현장설명회 참석완료 상태 변경 + │ + ▼ +2. QuoteService.upsertFromSiteBriefing() + │ + ▼ +3. 견적 자동 생성 (status: pending) + - 거래처, 현장 정보 복사 + - 금액 정보는 비어있음 + │ + ▼ +4. 담당자가 견적 편집 화면에서 상세 입력 + │ + ▼ +5. 저장 시 status: pending → draft 변경 +``` + +--- + +## 4. BOM 계산 로직 (10단계) + +### 4.1 계산 단계 개요 + +| 단계 | 명칭 | 설명 | +|------|------|------| +| 1 | 입력값수집 | W0, H0, QTY, 옵션값 수집 | +| 2 | 완제품선택 | 완제품 코드로 items 테이블 조회 | +| 3 | 변수계산 | W1, H1, AREA, WEIGHT 등 파생 변수 계산 | +| 4 | BOM전개 | 완제품의 BOM 트리 전개 | +| 5 | 단가출처 | 품목별 단가 조회 (prices 테이블) | +| 6 | 수량계산 | 수량 수식 평가 (변수 치환) | +| 7 | 금액계산 | 수량 × 단가 = 금액 | +| 8 | 카테고리그룹화 | item_category 기준 그룹화 | +| 9 | 소계계산 | 카테고리별 소계 | +| 10 | 최종합계 | 전체 합계 계산 | + +### 4.2 변수 계산 (Step 3) 상세 + +``` +기본 변수: +- W0: 개구부 폭 (mm) - 사용자 입력 +- H0: 개구부 높이 (mm) - 사용자 입력 +- QTY: 수량 - 사용자 입력 + +파생 변수 (스크린): +- W1 = W0 + 140 (제작 폭, 마진 140mm) +- H1 = H0 + 350 (제작 높이, 마진 350mm) +- M = (W1 × H1) / 1,000,000 (면적, ㎡) +- K = M × 2 + (W0 / 1000) × 14.17 (중량, kg) + +파생 변수 (철재): +- W1 = W0 + 110 (마진 110mm) +- H1 = H0 + 350 +- M = (W1 × H1) / 1,000,000 +- K = M × 25 (철재 중량) +``` + +### 4.3 수량 수식 예시 + +BOM의 quantity_formula 필드에 저장된 수식: + +``` +고정값: "1" +변수참조: "QTY" +계산식: "W1 / 1000" → 가이드레일 길이 + "CEIL(H1 / 2000)" → 분할 개수 + "M * 1.1" → 면적 기반 수량 (여유 10%) +``` + +--- + +## 5. 경동기업 전용 로직 + +### 5.1 적용 조건 + +```php +// tenant_id = 287 일 때만 적용 +private const KYUNGDONG_TENANT_ID = 287; + +if ($tenantId === self::KYUNGDONG_TENANT_ID) { + return $this->calculateKyungdongBom(...); +} +``` + +### 5.2 KyungdongFormulaHandler 주요 기능 + +| 기능 | 설명 | +|------|------| +| 모터 용량 계산 | 제품타입 × 인치 × 중량 3차원 조건표 | +| 브라켓 크기 결정 | 중량 기준 브라켓 인치 결정 | +| 절곡품 계산 | 10종 절곡품 (케이스, 가이드레일, 하단바 등) | +| 부자재 계산 | 3종 부자재 (볼트, 너트, 패킹 등) | + +### 5.3 경동기업 변수 계산 + +``` +기본 변수: +- W0, H0, QTY: 사용자 입력 +- bracket_inch: 브라켓 인치 (5", 6", 7") +- product_type: 제품 타입 (screen/steel) + +파생 변수: +- W1 = W0 + 140 +- H1 = H0 + 350 +- AREA = (W0 × (H0 + 550)) / 1,000,000 +- WEIGHT = AREA × 2 + (W0 / 1000) × 14.17 (스크린) + = AREA × 25 (철재) +- MOTOR_CAPACITY: 모터 용량 (조건표 조회) +- BRACKET_SIZE: 브라켓 크기 (조건표 조회) +``` + +--- + +## 6. 상태 관리 + +### 6.1 견적 상태 흐름 + +``` +pending ─────→ draft ─────→ finalized ─────→ converted +(견적대기) (작성중) (최종확정) (수주전환) + │ │ │ + │ ▼ │ + │ sent ────────────→│ + │ (발송됨) │ + │ │ │ + │ ▼ │ + │ approved │ + │ (승인됨) │ + │ │ │ + └──────────────┴──────────────┘ + │ + ▼ + rejected + (거절됨) +``` + +### 6.2 상태별 가능 작업 + +| 상태 | 수정 | 삭제 | 확정 | 수주전환 | +|------|------|------|------|----------| +| pending | ✓ | ✓ | - | - | +| draft | ✓ | ✓ | ✓ | - | +| sent | ✓ | ✓ | ✓ | - | +| approved | ✓ | ✓ | ✓ | - | +| finalized | - | - | - | ✓ | +| converted | - | - | - | - | +| rejected | ✓ | ✓ | - | - | + +--- + +## 7. 금액 계산 방식 + +### 7.1 카테고리 기반 단가 계산 + +CategoryGroup 모델을 사용하여 품목 카테고리별 단가 계산 방식 결정: + +| 카테고리 그룹 | 계산 방식 | 수식 | +|--------------|----------|------| +| area_based | 면적 기반 | 단가 × M (면적) | +| weight_based | 중량 기반 | 단가 × K (중량) | +| quantity_based | 수량 기반 | 단가 × 수량 | + +### 7.2 총 금액 계산 + +``` +material_cost = SUM(재료비 카테고리 품목의 total_price) +labor_cost = SUM(노무비 카테고리 품목의 total_price) +install_cost = SUM(설치비 카테고리 품목의 total_price) + +subtotal = material_cost + labor_cost + install_cost +discount_amount = subtotal × (discount_rate / 100) +total_amount = subtotal - discount_amount +``` + +### 7.3 단가 조회 우선순위 + +1. **prices 테이블** (Price::getSalesPriceByItemCode) +2. **items.attributes.salesPrice** (JSON 필드) +3. **기본값 0** + +--- + +## 8. 관련 파일 목록 + +### 8.1 백엔드 (API) + +``` +app/Services/Quote/ +├── QuoteService.php ← 견적 CRUD, 상태 관리 +├── QuoteCalculationService.php ← BOM 계산 진입점 +├── QuoteNumberService.php ← 견적번호 생성 +├── QuoteDocumentService.php ← 견적서/거래명세서 PDF +├── FormulaEvaluatorService.php ← 수식 평가, 10단계 계산 +└── Handlers/ + └── KyungdongFormulaHandler.php ← 경동기업 전용 + +app/Models/Quote/ +├── Quote.php ← 견적 마스터 모델 +├── QuoteItem.php ← 견적 품목 모델 +├── QuoteRevision.php ← 수정 이력 모델 +├── QuoteFormula.php ← 수식 정의 모델 +├── QuoteFormulaCategory.php +├── QuoteFormulaRange.php +├── QuoteFormulaMapping.php +└── QuoteFormulaItem.php + +database/migrations/ +├── 2025_12_04_164542_create_quotes_table.php +└── 2025_12_04_133410_create_quote_formula_tables.php +``` + +### 8.2 프론트엔드 (React) + +``` +react/src/components/quotes/ +├── types.ts ← 타입 정의 (LocationItem, BomCalculationResult) +├── actions.ts ← API 액션 (calculateBomBulk) +├── QuoteFooterBar.tsx ← 하단 버튼바 (견적서보기, 저장, 최종확정) +├── FormulaViewModal.tsx ← 수식 보기 모달 (개발용) +└── ... +``` + +--- + +## 9. 검증 체크리스트 + +### 9.1 데이터 정합성 검증 + +- [ ] quotes.total_amount = subtotal - discount_amount +- [ ] quotes.subtotal = material_cost + labor_cost + install_cost +- [ ] quote_items의 합계 = quotes의 비용 합계 +- [ ] calculation_inputs.bomResults의 grand_total = 품목 합계 + +### 9.2 상태 전이 검증 + +- [ ] pending → draft: 첫 수정 시 자동 전환 +- [ ] draft → finalized: 확정 버튼 클릭 + total_amount > 0 +- [ ] finalized → converted: 수주 전환 시 + order_id 설정 + +### 9.3 BOM 계산 검증 + +- [ ] W1 = W0 + 마진값 (SCREEN: 140, STEEL: 110) +- [ ] H1 = H0 + 350 +- [ ] 면적(M) = (W1 × H1) / 1,000,000 +- [ ] 중량(K) 계산식 제품타입별 확인 +- [ ] 수량 수식의 변수 치환 정확성 +- [ ] 단가 조회 우선순위 준수 + +--- + +*문서 작성일: 2026-01-29* +*작성자: Claude Code* \ No newline at end of file diff --git a/plans/api-analysis-report.md b/plans/api-analysis-report.md new file mode 100644 index 0000000..ae48343 --- /dev/null +++ b/plans/api-analysis-report.md @@ -0,0 +1,434 @@ +# SAM API 전체 분석 보고서 + +> **작성일**: 2026-01-29 +> **목적**: api/, mng/, react/ 프로젝트 간 API 중복/통합/미사용 분석 및 관계 정리 +> **기준 문서**: api/routes/api/v1/*.php, mng/routes/api.php, mng/routes/web.php, react/src/lib/api/* +> **상태**: ✅ 분석 완료 + +--- + +## 📍 분석 결과 요약 + +| 항목 | 수치 | +|------|------| +| **api/ 엔드포인트** | ~710+ | +| **mng/ 엔드포인트** | ~300+ | +| **React 실제 사용** | ~80+ (api/ 전체의 ~15%) | +| **중복 도메인** | 10개 | +| **즉시 정리 가능** | 3건 | +| **통합 가능 그룹** | 6개 | + +--- + +## 1. 개요 + +### 1.1 배경 + +SAM 프로젝트는 api/(REST API), mng/(관리자 패널), react/(프론트엔드) 3개 프로젝트로 구성되어 있으며, 각 프로젝트가 독립적으로 발전하면서 동일 도메인에 대한 API가 중복 생성되었다. 본 분석은 전체 API를 파악하고 정리 방안을 제시한다. + +### 1.2 분석 범위 + +| 프로젝트 | 역할 | 분석 대상 | +|---------|------|----------| +| **api/** | REST API 서버 | routes/api/v1/*.php (14개 라우트 파일), 컨트롤러 138개 | +| **mng/** | 관리자 패널 | routes/api.php, routes/web.php, 컨트롤러 90+개 | +| **react/** | 프론트엔드 | src/lib/api/*, src/hooks/*, src/app/api/* | + +--- + +## 2. 프로젝트별 API 구조 + +### 2.1 API 프로젝트 (api/) + +**라우트 파일**: `api/routes/api/v1/` + +| 도메인 | 라우트 파일 | 엔드포인트 수 | 소비자 | +|--------|-----------|:----------:|--------| +| 인증 | `auth.php` | 8 | React, MNG | +| 사용자 | `users.php` | 25 | React | +| 테넌트 | `tenants.php` | 18 | React | +| 관리자 | `admin.php` | 22 | React, MNG | +| 공통 | `common.php` | 95+ | React, MNG | +| HR | `hr.php` | 85+ | React | +| 재무 | `finance.php` | 130+ | React | +| 영업 | `sales.php` | 85+ | React | +| 재고 | `inventory.php` | 65+ | React | +| 생산 | `production.php` | 35+ | React | +| 설계 | `design.php` | 55+ | React | +| 파일 | `files.php` | 15 | React | +| 게시판 | `boards.php` | 70+ | React | +| 문서 | `documents.php` | 5+ | React | + +### 2.2 MNG 프로젝트 (mng/) + +**API 소비 방식**: 자체 모델로 DB 직접 접근 (api/ REST API를 거치지 않음) + +| 도메인 | 엔드포인트 수 | 비고 | +|--------|:----------:|------| +| 사용자/역할/권한 | 30+ | api/와 중복 | +| 메뉴/글로벌메뉴 | 25+ | api/와 중복 | +| 게시판/필드 | 20+ | api/와 중복 | +| 카테고리/글로벌 | 15+ | api/와 중복 | +| 바로빌 (전체) | 60+ | MNG 전용 (외부 서비스) | +| 프로젝트 관리 | 25+ | MNG 전용 | +| 견적 공식 | 30+ | MNG 전용 | +| 품목 필드 | 25+ | MNG 전용 | +| 문서/템플릿 | 12+ | api/와 중복 | +| 계좌/자금일정 | 18+ | api/와 중복 | +| 기타 (회의록, 신용, 영업, Lab) | 40+ | MNG 전용 | + +### 2.3 React 프론트엔드 (react/) + +**API 호출 방식**: Next.js Proxy (`/api/proxy/*`) → Backend (`/api/v1/*`) + +| 카테고리 | 주요 엔드포인트 | 사용 빈도 | +|---------|---------------|:--------:| +| 인증 | login, logout, refresh, signup | 높음 | +| 품목 CRUD | items, items/{id}, items/bom | 높음 | +| 품목기준관리 | item-master/* (pages, sections, fields) | 높음 | +| 견적 계산 | quotes/calculate, quotes/calculate/bom | 높음 | +| 공통코드 | settings/common/{group} | 높음 | +| 대시보드 | card-transactions/dashboard, loans/dashboard | 중간 | +| 알림 | today-issues/unread, unread/count | 중간 | +| 거래처 | clients, client-groups | 중간 | +| 재고 | stocks, work-results | 중간 | +| 일괄작업 | bulk-update-account-code | 낮음 | +| 내보내기 | attendances/export, salaries/export | 낮음 | + +--- + +## 3. 중복 API 분석 + +### 3.1 중복 컨트롤러 목록 (api/ vs mng/) + +| # | 도메인 | api/ 컨트롤러 | mng/ 컨트롤러 | 중복 수준 | +|---|--------|-------------|-------------|:--------:| +| 1 | 사용자 관리 | `Api\V1\Admin\AdminController` | `Api\Admin\UserController` | 🔴 높음 | +| 2 | 역할 관리 | `Api\V1\RoleController` | `Api\Admin\RoleController` | 🔴 높음 | +| 3 | 메뉴 관리 | `Api\V1\MenuController` | `Api\Admin\MenuController` | 🔴 높음 | +| 4 | 카테고리 | `Api\V1\CategoryController` | `Api\Admin\CategoryApiController` | 🔴 높음 | +| 5 | 계좌 관리 | `Api\V1\BankAccountController` | `Api\Admin\BankAccountController` | 🔴 높음 | +| 6 | 권한 관리 | `Api\V1\PermissionController` | `Api\Admin\PermissionController` | 🟡 중간 | +| 7 | 부서 관리 | `Api\V1\DepartmentController` | `Api\Admin\DepartmentController` | 🟡 중간 | +| 8 | 게시판 | `Api\V1\BoardController` | `Api\Admin\BoardController` | 🟡 중간 | +| 9 | 문서 | `Api\V1\Documents\DocumentController` | `Api\Admin\DocumentApiController` | 🟡 중간 | +| 10 | 테넌트 | `Api\V1\TenantController` | `Api\Admin\TenantController` | 🟡 중간 | + +### 3.2 상세 비교 + +#### (1) 사용자 관리 🔴 + +| 기능 | api/ | mng/ | 차이 | +|------|:----:|:----:|------| +| 기본 CRUD | ✅ | ✅ | 동일 | +| 복구 (restore) | ✅ | ✅ | 동일 | +| 비밀번호 초기화 | ✅ | ✅ | 동일 | +| 활성화/비활성화 | ✅ (PATCH /status) | ❌ | api/만 | +| 역할 부여/해제 | ✅ (POST/DELETE /roles) | ❌ | api/만 | +| 영구삭제 | ❌ | ✅ (DELETE /force) | mng/만 (슈퍼관리자) | +| 개발용 로그인토큰 | ❌ | ✅ (POST /login-token) | mng/만 | +| 모달 데이터 | ❌ | ✅ (GET /modal) | mng/만 | + +#### (2) 역할 관리 🔴 + +| 기능 | api/ | mng/ | 차이 | +|------|:----:|:----:|------| +| 기본 CRUD | ✅ | ✅ | 동일 | +| 통계 (stats) | ✅ | ❌ | api/만 | +| 활성 목록 (active) | ✅ | ❌ | api/만 | + +#### (3) 메뉴 관리 🔴 + +| 기능 | api/ | mng/ | 차이 | +|------|:----:|:----:|------| +| 기본 CRUD | ✅ | ✅ | 동일 | +| 순서변경 (reorder) | ✅ | ✅ | 동일 | +| 복구 (restore) | ✅ | ✅ | 동일 | +| 활성화 토글 | ✅ (toggle) | ✅ (toggle-active) | 동일 기능 | +| 동기화 | ✅ (sync, sync-new, sync-updates) | ❌ | api/만 | +| 트리 구조 | ❌ | ✅ (tree) | mng/만 | +| 글로벌 복사 | ❌ | ✅ (copy-from-global) | mng/만 | +| 일괄 작업 | ❌ | ✅ (bulk-delete/restore/force) | mng/만 | +| 숨김 토글 | ❌ | ✅ (toggle-hidden) | mng/만 | +| 영구삭제 | ❌ | ✅ (force) | mng/만 (슈퍼관리자) | + +#### (4) 카테고리 🔴 + +| 기능 | api/ | mng/ | 차이 | +|------|:----:|:----:|------| +| 기본 CRUD | ✅ | ✅ | 동일 | +| 트리/순서변경/이동 | ✅ | ✅ | 동일 | +| 활성화 토글 | ✅ | ✅ | 동일 | +| 필드 관리 | ✅ (fields CRUD, bulk-upsert) | ❌ | api/만 | +| 템플릿 관리 | ✅ (templates, apply, preview, diff) | ❌ | api/만 | +| 로그 조회 | ✅ (logs) | ❌ | api/만 | +| 글로벌 관리 | ❌ | ✅ (global-categories) | mng/만 | + +#### (5) 계좌 관리 🔴 + +| 기능 | api/ | mng/ | 차이 | +|------|:----:|:----:|------| +| 기본 CRUD | ✅ | ✅ | 동일 | +| 활성화 토글 | ✅ | ✅ | 동일 | +| 활성 목록 (active) | ✅ | ❌ | api/만 | +| 대표계좌 설정 | ✅ (set-primary) | ❌ | api/만 | +| 전체 조회 (all) | ❌ | ✅ | mng/만 | +| 요약 (summary) | ❌ | ✅ | mng/만 | +| 거래내역 | ❌ | ✅ (transactions) | mng/만 | +| 일괄 작업 | ❌ | ✅ (bulk-*) | mng/만 | +| 영구삭제/복구 | ❌ | ✅ (force/restore) | mng/만 | + +#### (6) 권한 관리 🟡 + +| 기능 | api/ | mng/ | 차이 | +|------|:----:|:----:|------| +| 권한 매트릭스 조회 | ✅ (dept/role/user menu-matrix) | ❌ | api/만 (특화) | +| 기본 CRUD | ❌ | ✅ | mng/만 | + +> **분석**: api/는 매트릭스 조회 전용, mng/는 CRUD 전용으로 기능 분리된 상태. 완전 중복은 아님. + +--- + +## 4. 통합 가능 API + +### 4.1 통합 대상 그룹 + +| # | 대상 | 현재 상태 | 통합 방안 | 우선순위 | +|---|------|----------|----------|:--------:| +| 1 | **인증 API** | signup + register 중복 | register 제거 (signup 유지) | 🔴 | +| 2 | **사용자 관리** | api/ + mng/ 각각 CRUD | mng/ → api/ 호출로 전환 | 🔴 | +| 3 | **역할 관리** | api/ + mng/ 각각 CRUD | api/에 통합, mng/는 호출만 | 🟡 | +| 4 | **메뉴 관리** | api/ 동기화 + mng/ 관리 분리 | 관리: mng/, 조회+동기화: api/ | 🟡 | +| 5 | **대시보드 데이터** | 개별 엔드포인트 분산 | 통합 대시보드 API 제공 | 🟢 | +| 6 | **일괄 업데이트** | withdrawals/deposits/sales 각각 | 공통 bulk-update 패턴 | 🟢 | + +### 4.2 인증 API 중복 상세 + +``` +현재: + POST /v1/login → 로그인 + POST /v1/logout → 로그아웃 + POST /v1/signup → 회원가입 (1) + POST /v1/register → 회원가입 (2) ← 중복! + POST /v1/token-login → 토큰 로그인 (MNG→DEV) + POST /v1/refresh → 토큰 갱신 + POST /v1/internal/exchange-token → 내부 서버 토큰 교환 + GET /v1/debug-apikey → 디버그용 ← 프로덕션 제거 필요 + +권장: + - register 제거 (signup 유지) + - debug-apikey 프로덕션 비활성화 +``` + +--- + +## 5. 미사용 API + +### 5.1 React에서 호출하지 않는 api/ 도메인 + +| 도메인 | 엔드포인트 수 | 미사용 이유 | +|--------|:----------:|-----------| +| HR 전체 (employees, attendance, leave, approval) | ~80+ | MNG에서 직접 관리 또는 React 미구현 | +| 생산 대부분 (processes, work-orders, inspections) | ~35+ | work-results만 사용 | +| 설계 전체 (models, versions, bom-templates) | ~55+ | 견적 계산 시 간접 사용만 | +| 재무 대부분 (cards, payroll, bad-debts 등) | ~100+ | CEO 대시보드 일부만 사용 | +| 사용자 초대 (invitations) | ~5 | React 미구현 | +| 알림 설정 (notification-settings) | ~5 | React 미구현 | +| 프로필 관리 (profiles) | ~5 | React 미구현 | +| 팝업 관리 (popups) | ~5 | React 미구현 | +| AI 리포트 (reports/ai) | ~4 | React 미구현 | +| 구독/결제 (subscriptions, payments) | ~20+ | React 미구현 | +| 현장/시공 (sites, construction) | ~30+ | React 미구현 | +| 검사 관리 (inspections) | ~8 | React 미구현 | + +> **참고**: "미사용"은 React 프론트엔드 기준. MNG에서 Blade UI로 직접 사용하거나 향후 구현 예정인 경우 포함. + +### 5.2 완전 미사용 가능성 높은 API + +| 엔드포인트 | 이유 | 조치 권장 | +|-----------|------|----------| +| `GET /v1/debug-apikey` | 디버그 전용 | 프로덕션 비활성화 | +| `POST /v1/register` | signup과 중복 | 제거 | +| `GET /v1/welfare/*` | React/MNG 모두 미호출 확인 필요 | 사용 여부 확인 | +| `GET /v1/entertainment/*` | React/MNG 모두 미호출 확인 필요 | 사용 여부 확인 | +| `GET /v1/calendar/schedules` | React 미호출 | 사용 여부 확인 | +| `GET /v1/comprehensive-analysis` | React 미호출 | 사용 여부 확인 | + +### 5.3 MNG 전용 기능 (정상) + +| 기능 | 설명 | 상태 | +|------|------|:----:| +| 바로빌 (Barobill) | 전자세금계산서, 카드, 홈택스 연동 | ✅ 정상 | +| 프로젝트 관리 | 프로젝트, 태스크, 이슈 | ✅ 정상 | +| 데일리 로그 | 일일 스크럼 | ✅ 정상 | +| 견적 공식 | 견적 계산 공식 관리 | ✅ 정상 | +| 회의록 | 녹음, AI 요약 (Google Cloud) | ✅ 정상 | +| 신용 평가 | Coocon API 연동 | ✅ 정상 | +| 영업 관리 | 매니저, 전망, 기록 | ✅ 정상 | +| DevTools | API 탐색기, 흐름 테스터 | ✅ 정상 | +| Lab/R&D | AI, 전략 실험 | ✅ 정상 | + +--- + +## 6. 프로젝트 간 API 관계도 + +### 6.1 시스템 구조 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 사용자 (브라우저) │ +│ │ +│ ┌──────────────┐ ┌──────────────────┐ │ +│ │ React App │ │ MNG Admin │ │ +│ │ (dev.sam.kr) │ │ (mng.sam.kr) │ │ +│ └──────┬───────┘ └──────┬───────────┘ │ +│ │ │ │ +│ Next.js Proxy 자체 모델 직접 사용 │ +│ (/api/proxy/*) + 일부 api/ 호출 │ +│ │ │ │ +│ ▼ │ │ +│ ┌──────────────┐ │ │ +│ │ API 서버 │◄─────────────────┘ │ +│ │ (api.sam.kr) │ token-login, │ +│ │ │ DevTools API 탐색 │ +│ └──────┬───────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────┐ │ +│ │ Database │◄──── MNG도 동일 DB 직접 접근 │ +│ │ (MySQL) │ │ +│ └──────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + +외부 API: +┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ +│ Google │ │ Coocon │ │ FCM │ │ NTS │ +│ Cloud │ │ (신용) │ │ (푸시) │ │ (홈택스) │ +└────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ + └────────────┴────────────┴─────────────┘ + │ + MNG에서 호출 +``` + +### 6.2 데이터 흐름 + +| 흐름 | 방식 | 설명 | +|------|------|------| +| React → API | HTTP (Proxy) | 모든 비즈니스 로직 API 호출 | +| MNG → DB | 직접 모델 | 관리 기능은 DB 직접 접근 | +| MNG → API | HTTP | token-login, DevTools, 일부 동기화 | +| MNG → 외부 | HTTP | Barobill, Google Cloud, Coocon, NTS | +| API → DB | 직접 모델 | 모든 비즈니스 로직 | + +### 6.3 중복 발생 원인 + +``` +문제: MNG가 api/를 호출하지 않고 DB 직접 접근 + → 동일 도메인에 대해 api/, mng/ 각각 독립 컨트롤러 보유 + → 비즈니스 로직 분산, 유지보수 부담 증가 + +현재: + React → api/ (REST API) → DB + MNG → DB 직접 ← 여기가 문제 + +이상적: + React → api/ (REST API) → DB + MNG → api/ (REST API) → DB (관리자 전용 엔드포인트 추가) +``` + +--- + +## 7. 개선 권장사항 + +### 7.1 즉시 정리 (Quick Wins) 🔴 + +| # | 작업 | 영향 | 노력 | +|---|------|------|:----:| +| 1 | `POST /v1/register` 제거 (signup 유지) | 코드 정리 | 소 | +| 2 | `GET /v1/debug-apikey` 프로덕션 비활성화 | 보안 강화 | 소 | +| 3 | 미사용 Swagger 문서 정리 | 문서 정확성 | 소 | + +### 7.2 중복 해소 (Medium Term) 🟡 + +| # | 작업 | 현재 | 목표 | +|---|------|------|------| +| 1 | 사용자 관리 통합 | api/ + mng/ 각각 | api/ 마스터, mng/ 관리자 기능만 추가 | +| 2 | 역할 관리 통합 | api/ + mng/ 각각 | api/ 단일 소스 | +| 3 | 카테고리 통합 | api/ + mng/ 각각 | api/ 마스터, mng/ 글로벌 관리만 유지 | +| 4 | 계좌 관리 통합 | api/ + mng/ 각각 | 하나로 통합 | +| 5 | 메뉴 관리 정리 | api/ 동기화 + mng/ 관리 | 역할 분리 명확화 | + +### 7.3 아키텍처 개선 (Long Term) 🟢 + +| # | 작업 | 설명 | +|---|------|------| +| 1 | MNG → API 호출 전환 | MNG가 DB 직접 접근 대신 api/ REST API 호출 | +| 2 | API Gateway 도입 | 인증/권한/레이트리밋 중앙 관리 | +| 3 | 미사용 API 비활성화 | deprecation 헤더 추가 후 단계적 제거 | +| 4 | API v2 전환 | 중복 정리 포함한 v2 설계 | + +--- + +## 8. 전체 엔드포인트 도메인별 수 + +### API 프로젝트 + +| 도메인 | 파일 | 수 | +|--------|------|:--:| +| 인증 | auth.php | 8 | +| 사용자 | users.php | 25 | +| 테넌트 | tenants.php | 18 | +| 관리자 | admin.php | 22 | +| 공통 | common.php | 95+ | +| HR | hr.php | 85+ | +| 재무 | finance.php | 130+ | +| 영업 | sales.php | 85+ | +| 재고 | inventory.php | 65+ | +| 생산 | production.php | 35+ | +| 설계 | design.php | 55+ | +| 파일 | files.php | 15 | +| 게시판 | boards.php | 70+ | +| 문서 | documents.php | 5+ | +| **합계** | | **~710+** | + +### MNG 프로젝트 + +| 그룹 | 수 | +|------|:--:| +| 사용자/역할/권한 | 30+ | +| 메뉴/글로벌메뉴 | 25+ | +| 게시판/필드 | 20+ | +| 카테고리/글로벌 | 15+ | +| 바로빌 | 60+ | +| 프로젝트 관리 | 25+ | +| 견적 공식 | 30+ | +| 품목 필드 | 25+ | +| 문서/템플릿 | 12+ | +| 계좌/자금일정 | 18+ | +| 기타 | 40+ | +| **합계** | **~300+** | + +--- + +## 9. 참고 문서 + +- `docs/standards/api-rules.md` - API 규칙 +- `docs/architecture/system-overview.md` - 시스템 아키텍처 +- `docs/specs/database-schema.md` - DB 스키마 +- `api/routes/api/v1/*.php` - API 라우트 파일 +- `mng/routes/api.php` - MNG API 라우트 +- `react/src/lib/api/` - React API 클라이언트 + +--- + +## 10. 결론 + +1. **api/와 mng/의 10개 도메인에서 컨트롤러 중복** 발생 - 동일 DB를 각각 직접 접근하는 구조적 문제 +2. **React는 api/ 전체의 약 15%만 사용** - 나머지는 MNG 전용이거나 미구현 기능 +3. **인증 API에 signup/register 중복** 존재 - 즉시 정리 가능 +4. **장기적으로 MNG → API 호출 전환**이 이상적이나, 현재 아키텍처도 기능적으로 동작 +5. **Quick Wins(register 제거, debug-apikey 비활성화)부터 시작** 권장 + +--- + +*이 문서는 /sc:plan 스킬로 생성되었습니다.* \ No newline at end of file diff --git a/plans/quote-management-url-migration-plan.md b/plans/quote-management-url-migration-plan.md index 23b4329..9f2ce61 100644 --- a/plans/quote-management-url-migration-plan.md +++ b/plans/quote-management-url-migration-plan.md @@ -1,4 +1,4 @@ -ㅅ# 견적관리 URL 구조 마이그레이션 계획 +# 견적관리 URL 구조 마이그레이션 계획 > **작성일**: 2026-01-26 > **목적**: 견적관리 페이지 URL 구조를 Query 기반(?mode=new)에서 RESTful 경로 기반(/test-new, /test/[id])으로 마이그레이션 diff --git a/plans/tenant-id-compliance-plan.md b/plans/tenant-id-compliance-plan.md new file mode 100644 index 0000000..824869f --- /dev/null +++ b/plans/tenant-id-compliance-plan.md @@ -0,0 +1,410 @@ +# tenant_id 준수 분석 및 분리 방안 + +> **작성일**: 2026-01-29 +> **목적**: API 전체 모델에서 tenant_id 스코핑 미적용 현황을 분석하고, BelongsToTenant trait 적용 방안 수립 +> **기준 문서**: `docs/specs/database-schema.md`, `docs/architecture/system-overview.md` +> **상태**: 🔄 분석 완료 → 실행 대기 + +--- + +## 📍 현재 진행 상태 + +| 항목 | 내용 | +|------|------| +| **마지막 완료 작업** | 전체 모델 분석 완료 | +| **다음 작업** | 사용자 검토 후 Phase 1 실행 | +| **진행률** | 0/4 Phase (0%) | +| **마지막 업데이트** | 2026-01-29 | + +--- + +## 1. 개요 + +### 1.1 배경 + +SAM API는 멀티테넌트 아키텍처를 사용하며, `BelongsToTenant` trait를 통해 자동 tenant_id 스코핑을 적용합니다. 그러나 일부 모델에서 trait가 누락되어 있어, 테넌트 간 데이터 격리가 보장되지 않을 수 있습니다. + +**분석 결과 요약:** +- 전체 모델: 167개 +- BelongsToTenant 적용: 103개 (61.7%) +- 미적용: 63개 (37.7%) + - 의도적 글로벌: 18개 + - 부모 종속 (FK 격리): 13개 + - **BelongsToTenant 추가 필요: 27개** + - **검토 후 결정: 5개** + +### 1.2 기준 원칙 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 🎯 핵심 원칙 │ +├─────────────────────────────────────────────────────────────────┤ +│ 1. tenant_id 컬럼이 있는 모델은 BelongsToTenant 적용 필수 │ +│ 2. 부모-자식 관계에서 자식은 부모의 FK로 격리 가능하면 면제 │ +│ 3. 시스템 전역 데이터(User, Tenant, ApiKey 등)는 글로벌 유지 │ +│ 4. Boards 영역은 시스템/테넌트 혼용이므로 커스텀 스코프 유지 │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### 1.3 변경 승인 정책 + +| 분류 | 예시 | 승인 | +|------|------|------| +| ✅ 즉시 가능 | BelongsToTenant trait 추가 (기존 동작 유지) | 불필요 | +| ⚠️ 컨펌 필요 | Boards 영역 스코핑 방식 변경, 쿼리 로직 수정 | **필수** | +| 🔴 금지 | 테이블 구조 변경, tenant_id 컬럼 추가/삭제 | 별도 협의 | + +### 1.4 준수 규칙 +- `docs/specs/database-schema.md` - 테이블 구조 +- `docs/architecture/system-overview.md` - 시스템 아키텍처 +- `docs/standards/quality-checklist.md` - 품질 체크리스트 + +--- + +## 2. 분석 결과 상세 + +### 2.1 의도적 글로벌 (BelongsToTenant 불필요) - 18개 + +시스템 전역 데이터로, tenant_id 스코핑이 필요하지 않습니다. + +| # | 모델 | 테이블 | tenant_id | 사유 | +|---|------|--------|:---------:|------| +| 1 | `ApiKey` | api_keys | ❌ | 시스템 API 키 | +| 2 | `ApiRequestLog` | api_request_logs | ❌ | 시스템 감시 로그 | +| 3 | `AuditLog` | audit_logs | ✅ | 전체 감사 로그 (자체 tenant_id 필터링) | +| 4 | `FcmSendLog` | fcm_send_logs | ✅ | 푸시 발송 로그 (수동 필터링) | +| 5 | `LoginToken` | login_tokens | ❌ | MNG→API 인증 토큰 | +| 6 | `SiteAdmin` | site_admins | ❌ | 시스템 관리자 | +| 7 | `Tenant` | tenants | ❌ | 테넌트 마스터 (자기 자신) | +| 8 | `Plan` | plans | ❌ | 구독 플랜 정의 | +| 9 | `Subscription` | subscriptions | ✅ | 구독 (tenant_id로 연결) | +| 10 | `Payment` | payments | ✅ | 결제 (tenant_id로 연결) | +| 11 | `User` | users | ❌ | 글로벌 사용자 계정 | +| 12 | `UserTenant` | user_tenants | ✅ | 사용자-테넌트 매핑 (피벗) | +| 13 | `UserRole` | user_roles | ✅ | 사용자-역할 매핑 (피벗) | +| 14 | `GlobalMenu` | global_menus | ❌ | 시스템 전역 메뉴 | +| 15 | `Tag` | tags | ❌ | 시스템 공용 태그 | +| 16 | `SystemFieldDefinition` | system_field_definitions | ❌ | 시스템 필드 정의 | +| 17 | `KdPriceTable` | kd_price_tables | ❌ | 경동 전용 단가표 (레거시) | +| 18 | `TenantScope` | - | - | 스코프 정의 클래스 (모델 아님) | + +### 2.2 부모 종속 (FK 격리 - BelongsToTenant 면제) - 13개 + +부모 모델에 BelongsToTenant가 적용되어 있고, FK를 통해 자동 격리되는 자식 모델입니다. + +| # | 모델 | 테이블 | 부모 모델 | FK | +|---|------|--------|----------|-----| +| 1 | `PostCustomFieldValue` | post_custom_field_values | Post | post_id | +| 2 | `DocumentData` | document_data | Document | document_id | +| 3 | `DocumentApproval` | document_approvals | Document | document_id | +| 4 | `DocumentAttachment` | document_attachments | Document | document_id | +| 5 | `RoleMenuPermission` | role_menu_permissions | Role | role_id | +| 6 | `UserMenuPermission` | user_menu_permissions | - | user_id + menu_id | +| 7 | `NotificationSettingGroupItem` | notification_setting_group_items | NotificationSettingGroup | group_id | +| 8 | `BillInstallment` | bill_installments | Bill | bill_id | +| 9 | `ApprovalStep` | approval_steps | Approval | approval_id | +| 10 | `OrderItemComponent` | order_item_components | OrderItem | order_item_id | +| 11 | `MaterialInspectionItem` | inspection_items | MaterialInspection | inspection_id | +| 12 | `QuoteFormulaItem` | quote_formula_items | QuoteFormula | formula_id | +| 13 | `QuoteFormulaRange` | quote_formula_ranges | QuoteFormula | formula_id | + +**주의:** 이 모델들은 직접 쿼리할 때 부모를 통한 접근이 필수입니다. 직접 `::all()` 등으로 접근하면 테넌트 격리가 안됩니다. + +### 2.3 🔴 BelongsToTenant 추가 필요 - 27개 + +tenant_id 컬럼이 있지만 BelongsToTenant trait가 없는 모델입니다. 추가해야 합니다. + +| # | 모델 | 테이블 | 영역 | 우선순위 | +|---|------|--------|------|:--------:| +| **Design 영역 (4개)** | | | | | +| 1 | `DesignModel` | models | 설계 | 높음 | +| 2 | `ModelVersion` | model_versions | 설계 | 높음 | +| 3 | `BomTemplate` | bom_templates | 설계 | 높음 | +| 4 | `BomTemplateItem` | bom_template_items | 설계 | 높음 | +| **Orders 영역 (2개)** | | | | | +| 5 | `OrderHistory` | order_histories | 수주 | 높음 | +| 6 | `OrderVersion` | order_versions | 수주 | 높음 | +| **Materials 영역 (3개)** | | | | | +| 7 | `MaterialReceipt` | material_receipts | 자재 | 높음 | +| 8 | `MaterialInspection` | inspections | 자재 | 높음 | +| 9 | `MaterialInspectionItem` | inspection_items | 자재 | 중간 | +| **Tenants 설정 영역 (7개)** | | | | | +| 10 | `TenantOptionGroup` | tenant_option_groups | 설정 | 높음 | +| 11 | `TenantOptionValue` | tenant_option_values | 설정 | 높음 | +| 12 | `TenantFieldSetting` | tenant_field_settings | 설정 | 높음 | +| 13 | `TenantUserProfile` | tenant_user_profiles | 설정 | 중간 | +| 14 | `SettingFieldDef` | setting_field_defs | 설정 | 중간 | +| 15 | `TenantStatField` | tenant_stat_fields | 설정 | 중간 | +| 16 | `BarobillSetting` | barobill_settings | 설정 | 낮음 | +| **BadDebts 영역 (2개)** | | | | | +| 17 | `BadDebtDocument` | bad_debt_documents | 미수금 | 중간 | +| 18 | `BadDebtMemo` | bad_debt_memos | 미수금 | 중간 | +| **Permissions 영역 (2개)** | | | | | +| 19 | `Permission` | permissions | 권한 | 높음 | +| 20 | `PermissionOverride` | permission_overrides | 권한 | 높음 | +| **기타 영역 (8개)** | | | | | +| 21 | `MainRequest` | main_requests | 요청 | 높음 | +| 22 | `MainRequestFlow` | main_request_flows | 요청 | 높음 | +| 23 | `MainRequestEstimate` | main_request_estimates | 견적 | 높음 | +| 24 | `Schedule` | schedules | 일정 | 중간 | +| 25 | `ProcessItem` | process_items | 공정 | 중간 | +| 26 | `ProcessClassificationRule` | process_classification_rules | 공정 | 중간 | +| 27 | `ItemDetail` | item_details | 품목 | 중간 | + +### 2.4 ⚠️ 검토 후 결정 필요 - 5개 + +커스텀 스코핑을 사용하거나, 특수한 비즈니스 로직이 있는 모델입니다. + +| # | 모델 | 현재 방식 | 검토 사항 | +|---|------|----------|----------| +| 1 | `Board` | `scopeAccessible()` 커스텀 | tenant_id nullable; 시스템 게시판(tenant_id=null)과 테넌트 게시판 혼용 | +| 2 | `Post` | 수동 where 조건 | Board의 tenant_id 정책을 따름 | +| 3 | `BoardComment` | 수동 where 조건 | Post 종속 | +| 4 | `BoardSetting` | 수동 where 조건 | Board 종속 | +| 5 | `CompanyRequest` | `created_tenant_id` | tenant_id 대신 created_tenant_id 사용 | + +**Board 영역 결론:** 시스템 게시판(tenant_id=null)을 지원해야 하므로, BelongsToTenant의 글로벌 스코프 대신 현재 커스텀 스코프(`scopeAccessible`) 유지가 적합합니다. + +### 2.5 특수 케이스 + +| 모델 | 설명 | +|------|------| +| `Part` | tenant_id 있지만 BelongsToTenant 미적용. Product와 유사 역할이나 별도 테이블 | +| `Lot` / `LotSale` | tenant_id 있지만 미적용. 품질관리 영역 | +| `CalculationConfig` | tenant_id 있지만 미적용. 계산 설정 | +| `QuoteFormulaMapping` | tenant_id 있지만 미적용. 견적 수식 매핑 | + +--- + +## 3. 작업 절차 + +### 3.1 단계별 절차 + +``` +Phase 1: 고우선순위 모델 적용 (15개) +├── Design 영역: DesignModel, ModelVersion, BomTemplate, BomTemplateItem +├── Orders 영역: OrderHistory, OrderVersion +├── Materials 영역: MaterialReceipt, MaterialInspection +├── Permissions 영역: Permission, PermissionOverride +├── MainRequest 영역: MainRequest, MainRequestFlow, MainRequestEstimate +├── Tenants 설정: TenantOptionGroup, TenantOptionValue, TenantFieldSetting +└── 검증: 기존 서비스 로직에 중복 where 조건 확인 및 정리 + +Phase 2: 중간 우선순위 모델 적용 (10개) +├── Tenants 설정: TenantUserProfile, SettingFieldDef, TenantStatField +├── BadDebts: BadDebtDocument, BadDebtMemo +├── 기타: Schedule, ProcessItem, ProcessClassificationRule, ItemDetail +├── Materials: MaterialInspectionItem +└── 검증: 서비스 로직 정리 + +Phase 3: 특수 케이스 처리 (4개) +├── Part, Lot, LotSale, CalculationConfig, QuoteFormulaMapping +└── 각 모델별 비즈니스 로직 확인 후 적용 + +Phase 4: 서비스 레이어 정리 +├── BelongsToTenant 적용 후 불필요한 수동 where('tenant_id', ...) 제거 +├── 직접 쿼리하는 부모 종속 모델에 대한 접근 패턴 검토 +└── 전체 통합 테스트 +``` + +--- + +## 4. 상세 작업 내용 + +### 4.1 Phase 1: 고우선순위 (15개) + +각 모델에 `use BelongsToTenant;` 추가. 작업 패턴: + +```php +// Before +namespace App\Models\Design; + +use App\Traits\ModelTrait; +use Illuminate\Database\Eloquent\Model; + +class DesignModel extends Model +{ + use ModelTrait; + // ... +} + +// After +namespace App\Models\Design; + +use App\Traits\BelongsToTenant; +use App\Traits\ModelTrait; +use Illuminate\Database\Eloquent\Model; + +class DesignModel extends Model +{ + use BelongsToTenant, ModelTrait; + // ... +} +``` + +**Phase 1 대상 모델:** + +| # | 파일 경로 | 상태 | +|---|----------|:----:| +| 1.1 | `app/Models/Design/DesignModel.php` | ⏳ | +| 1.2 | `app/Models/Design/ModelVersion.php` | ⏳ | +| 1.3 | `app/Models/Design/BomTemplate.php` | ⏳ | +| 1.4 | `app/Models/Design/BomTemplateItem.php` | ⏳ | +| 1.5 | `app/Models/Orders/OrderHistory.php` | ⏳ | +| 1.6 | `app/Models/Orders/OrderVersion.php` | ⏳ | +| 1.7 | `app/Models/Materials/MaterialReceipt.php` | ⏳ | +| 1.8 | `app/Models/Materials/MaterialInspection.php` | ⏳ | +| 1.9 | `app/Models/Permissions/Permission.php` | ⏳ | +| 1.10 | `app/Models/Permissions/PermissionOverride.php` | ⏳ | +| 1.11 | `app/Models/MainRequest.php` | ⏳ | +| 1.12 | `app/Models/MainRequestFlow.php` | ⏳ | +| 1.13 | `app/Models/Estimates/MainRequestEstimate.php` | ⏳ | +| 1.14 | `app/Models/Tenants/TenantOptionGroup.php` | ⏳ | +| 1.15 | `app/Models/Tenants/TenantOptionValue.php` | ⏳ | +| 1.16 | `app/Models/Tenants/TenantFieldSetting.php` | ⏳ | + +### 4.2 Phase 2: 중간 우선순위 (10개) + +| # | 파일 경로 | 상태 | +|---|----------|:----:| +| 2.1 | `app/Models/Tenants/TenantUserProfile.php` | ⏳ | +| 2.2 | `app/Models/Tenants/SettingFieldDef.php` | ⏳ | +| 2.3 | `app/Models/Tenants/TenantStatField.php` | ⏳ | +| 2.4 | `app/Models/BadDebts/BadDebtDocument.php` | ⏳ | +| 2.5 | `app/Models/BadDebts/BadDebtMemo.php` | ⏳ | +| 2.6 | `app/Models/Tenants/Schedule.php` | ⏳ | +| 2.7 | `app/Models/ProcessItem.php` | ⏳ | +| 2.8 | `app/Models/ProcessClassificationRule.php` | ⏳ | +| 2.9 | `app/Models/Items/ItemDetail.php` | ⏳ | +| 2.10 | `app/Models/Materials/MaterialInspectionItem.php` | ⏳ | + +### 4.3 Phase 3: 특수 케이스 (5개) + +| # | 파일 경로 | 검토 사항 | 상태 | +|---|----------|----------|:----:| +| 3.1 | `app/Models/Products/Part.php` | Product와 역할 중복 여부 | ⏳ | +| 3.2 | `app/Models/Qualitys/Lot.php` | 품질관리 독립 쿼리 패턴 | ⏳ | +| 3.3 | `app/Models/Qualitys/LotSale.php` | Lot 종속 여부 | ⏳ | +| 3.4 | `app/Models/Calculation/CalculationConfig.php` | 테넌트별 설정 확인 | ⏳ | +| 3.5 | `app/Models/Quote/QuoteFormulaMapping.php` | 공용 vs 테넌트별 | ⏳ | + +### 4.4 Phase 4: 서비스 레이어 정리 + +BelongsToTenant 적용 후, 서비스에서 수동으로 `->where('tenant_id', $tenantId)` 하던 코드를 정리합니다. + +**확인 대상 서비스:** +- `app/Services/Design/` - DesignModelService, BomTemplateService 등 +- `app/Services/OrderService.php` - OrderHistory, OrderVersion 관련 +- `app/Services/Materials/` - MaterialReceiptService 등 +- `app/Services/MainRequestService.php` +- 기타 관련 서비스 + +**정리 패턴:** +```php +// Before (수동 스코핑) +$models = DesignModel::where('tenant_id', $this->tenantId())->get(); + +// After (BelongsToTenant 자동 스코핑) +$models = DesignModel::all(); // 글로벌 스코프가 자동 적용 +``` + +--- + +## 5. 컨펌 대기 목록 + +| # | 항목 | 변경 내용 | 영향 범위 | 상태 | +|---|------|----------|----------|------| +| 1 | Boards 영역 | 현재 커스텀 스코프 유지 vs BelongsToTenant 전환 | Board, Post, BoardComment, BoardSetting | ⚠️ 확인 필요 | +| 2 | Permission 영역 | Spatie Permission 패키지와의 호환성 | Permission, PermissionOverride | ⚠️ 확인 필요 | +| 3 | Schedule 모델 | tenant_id nullable - 글로벌 일정 지원 유지 여부 | Schedule | ⚠️ 확인 필요 | +| 4 | CompanyRequest | created_tenant_id → tenant_id 통일 여부 | CompanyRequest | ⚠️ 확인 필요 | + +--- + +## 6. 변경 이력 + +| 날짜 | 항목 | 변경 내용 | 파일 | 승인 | +|------|------|----------|------|------| +| 2026-01-29 | 분석 | 전체 모델 tenant_id 준수 분석 완료 | 167개 모델 분석 | - | +| 2026-01-29 | 문서 | 계획 문서 초안 작성 | docs/plans/tenant-id-compliance-plan.md | - | + +--- + +## 7. 참고 문서 + +- **DB 스키마**: `docs/specs/database-schema.md` +- **시스템 아키텍처**: `docs/architecture/system-overview.md` +- **API 규칙**: `docs/standards/api-rules.md` +- **품질 체크리스트**: `docs/standards/quality-checklist.md` +- **BelongsToTenant trait**: `api/app/Traits/BelongsToTenant.php` +- **TenantScope**: `api/app/Models/Scopes/TenantScope.php` + +--- + +## 8. 리스크 및 주의사항 + +### 8.1 BelongsToTenant 적용 시 부작용 + +| 리스크 | 설명 | 대응 | +|--------|------|------| +| **중복 스코핑** | 서비스에서 이미 `where('tenant_id', ...)` 하고 있으면 중복 | Phase 4에서 수동 where 제거 | +| **withoutGlobalScope 필요** | 관리자 기능에서 전체 데이터 조회 시 | `withoutGlobalScopes()` 명시 | +| **테스트 실패** | tenant_id 컨텍스트 없는 테스트 | 테스트에서 tenant context 설정 | +| **마이그레이션 영향** | seeder/migration에서 직접 insert | tenant context 없이 실행 가능한지 확인 | + +### 8.2 Permission 모델 특수 사항 + +Permission은 Spatie Permission 패키지를 확장하므로, BelongsToTenant 적용 시 패키지 내부 쿼리와 충돌 가능성이 있습니다. 적용 전 테스트가 필수입니다. + +### 8.3 Schedule 모델 특수 사항 + +Schedule의 tenant_id는 nullable입니다. BelongsToTenant 적용 시 tenant_id=null인 글로벌 일정이 조회되지 않을 수 있습니다. TenantScope에서 nullable 처리가 필요할 수 있습니다. + +--- + +## 9. 검증 결과 + +> 작업 완료 후 이 섹션에 검증 결과 추가 + +### 9.1 성공 기준 + +| 기준 | 측정 방법 | 달성 | +|------|----------|:----:| +| Phase 1 모델 전체 BelongsToTenant 적용 | 코드 확인 | ⏳ | +| Phase 2 모델 전체 BelongsToTenant 적용 | 코드 확인 | ⏳ | +| 서비스 레이어 중복 where 제거 | grep 검색 | ⏳ | +| 기존 API 기능 정상 동작 | Swagger 테스트 | ⏳ | +| Pint 포맷팅 통과 | `./vendor/bin/pint --test` | ⏳ | + +--- + +## 10. 자기완결성 점검 결과 + +### 10.1 체크리스트 검증 + +| # | 검증 항목 | 상태 | 비고 | +|---|----------|:----:|------| +| 1 | 작업 목적이 명확한가? | ✅ | tenant_id 미적용 모델에 BelongsToTenant 추가 | +| 2 | 성공 기준이 정의되어 있는가? | ✅ | 섹션 9.1 참조 | +| 3 | 작업 범위가 구체적인가? | ✅ | 27개 모델 명시, 5개 검토 대상 분리 | +| 4 | 의존성이 명시되어 있는가? | ✅ | Spatie Permission, Boards 커스텀 스코프 | +| 5 | 참고 파일 경로가 정확한가? | ✅ | 전체 모델 파일 경로 명시 | +| 6 | 단계별 절차가 실행 가능한가? | ✅ | Phase 1~4 구체적 작업 항목 | +| 7 | 검증 방법이 명시되어 있는가? | ✅ | 성공 기준 및 테스트 방법 | +| 8 | 모호한 표현이 없는가? | ✅ | 구체적 모델명, 파일 경로, 수치 사용 | + +### 10.2 새 세션 시뮬레이션 테스트 + +| 질문 | 답변 가능 | 참조 섹션 | +|------|:--------:|----------| +| Q1. 이 작업의 목적은 무엇인가? | ✅ | 1.1 배경 | +| Q2. 어디서부터 시작해야 하는가? | ✅ | 4.1 Phase 1 | +| Q3. 어떤 파일을 수정해야 하는가? | ✅ | 2.3 수정 필요 목록 + 4.1~4.3 파일 경로 | +| Q4. 작업 완료 확인 방법은? | ✅ | 9.1 성공 기준 | +| Q5. 막혔을 때 참고 문서는? | ✅ | 7. 참고 문서 | + +**결과**: 5/5 통과 → ✅ 자기완결성 확보 + +--- + +*이 문서는 /sc:plan 스킬로 생성되었습니다.* \ No newline at end of file