docs:견적관리 분석 문서 및 INDEX 업데이트

- INDEX.md 업데이트
- 견적관리 URL 마이그레이션 계획 수정
- API 분석 리포트, tenant-id 준수 계획 추가
- 견적관리 기능 문서 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-01 20:37:04 +09:00
parent 41a2c442c6
commit 2bbf220dc8
5 changed files with 1302 additions and 1 deletions

View File

@@ -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/ - 프로젝트별 문서

455
features/quotes/README.md Normal file
View File

@@ -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*

View File

@@ -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 스킬로 생성되었습니다.*

View File

@@ -1,4 +1,4 @@
# 견적관리 URL 구조 마이그레이션 계획
# 견적관리 URL 구조 마이그레이션 계획
> **작성일**: 2026-01-26
> **목적**: 견적관리 페이지 URL 구조를 Query 기반(?mode=new)에서 RESTful 경로 기반(/test-new, /test/[id])으로 마이그레이션

View File

@@ -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 스킬로 생성되었습니다.*