# FG 제품코드 통합 계획 > **작성일**: 2026-02-19 > **목적**: FG 제품코드에서 설치유형/마감재질을 분리하여 위치별 설정으로 이동, 18개 FG 품목을 6개로 통합 > **기준 문서**: `docs/rules/item-policy.md`, `docs/features/quotes/README.md` > **상태**: 🔄 진행중 --- ## 📍 현재 진행 상태 | 항목 | 내용 | |------|------| | **마지막 완료 작업** | 영향도 분석 완료, 혼합형 validation 수정 커밋 완료 | | **다음 작업** | Phase 1: DB 마이그레이션 | | **진행률** | 0/8 (0%) | | **마지막 업데이트** | 2026-02-19 | --- ## 1. 개요 ### 1.1 배경 현재 경동기업(tenant_id=287) FG 품목 코드 체계: ``` FG-KWE01-벽면형-SUS (모델: KWE01, 설치유형: 벽면형, 마감재질: SUS) FG-KWE01-벽면형-EGI (모델: KWE01, 설치유형: 벽면형, 마감재질: EGI) FG-KWE01-측면형-SUS (모델: KWE01, 설치유형: 측면형, 마감재질: SUS) ... (총 18개 = 6모델 × {벽면형,측면형} × {SUS,EGI} + 혼합형 추가 예정) ``` 문제점: - 설치유형/마감재질은 **위치(Location)별 설정**이지 제품 자체의 속성이 아님 - 같은 모델(KWE01)인데 FG 코드가 4개 이상으로 분산 - 혼합형 추가 시 FG 품목이 계속 늘어남 (6모델 × 3설치유형 × 2마감재질 = 36개) ### 1.2 목표 코드 체계 ``` AS-IS: FG-KWE01-벽면형-SUS → TO-BE: KWE01 ``` - "FG-" 접두사 제거: `item_type = 'FG'` 컬럼이 이미 완제품 구분 담당 - 설치유형(벽면형/측면형/혼합형) 제거: 위치별 `guideRailType` 파라미터로 전달 - 마감재질(SUS/EGI) 제거: 위치별 `finishingType` 파라미터로 전달 ### 1.3 기준 원칙 ``` ┌─────────────────────────────────────────────────────────────────┐ │ 🎯 핵심 원칙 │ ├─────────────────────────────────────────────────────────────────┤ │ 1. 코어 계산 로직(KyungdongFormulaHandler) 변경 없음 │ │ 2. BOM은 child_item_id FK 기반 → 코드 변경에 안전 │ │ 3. product_model/finishing_type은 이미 별도 파라미터 전달 중 │ │ 4. 기존 quote_items에 FG 코드 참조 데이터 없음 (마이그레이션 부담 ↓) │ └─────────────────────────────────────────────────────────────────┘ ``` ### 1.4 변경 승인 정책 | 분류 | 예시 | 승인 | |------|------|------| | ✅ 즉시 가능 | React UI에 마감재질 Select 추가, validation 규칙 수정 | 불필요 | | ⚠️ 컨펌 필요 | items 테이블 데이터 통합, BOM parent_item_id 재매핑, 시더 수정 | **필수** | | 🔴 금지 | items 테이블 스키마 변경, 기존 BOM 삭제, 견적 계산 코어 로직 변경 | 별도 협의 | ### 1.5 준수 규칙 - `docs/rules/item-policy.md` - 품목 정책 - `docs/standards/quality-checklist.md` - 품질 체크리스트 - `docs/features/quotes/README.md` - 견적 시스템 --- ## 2. 대상 범위 ### 2.1 Phase 1: DB 마이그레이션 (items 통합) | # | 작업 항목 | 상태 | 비고 | |---|----------|:----:|------| | 1.1 | 18개 FG 품목 → 6개로 통합 마이그레이션 스크립트 | ⏳ | items.code 변경 | | 1.2 | BOM parent_item_id 재매핑 | ⏳ | 통합된 item_id로 변경 | | 1.3 | 통합 대상 외 12개 FG 품목 soft delete | ⏳ | 연결된 BOM 확인 후 | | 1.4 | MapItemsToProcesses globalExcludes 수정 | ⏳ | 'FG-%' → item_type 기반 | ### 2.2 Phase 2: API 수정 | # | 작업 항목 | 상태 | 비고 | |---|----------|:----:|------| | 2.1 | FormulaEvaluatorService: finishing_type 파라미터 수신 | ⏳ | 마감재질 매핑 추가 | | 2.2 | QuoteBomCalculateRequest: finishingType validation 추가 | ⏳ | SUS/EGI | | 2.3 | QuoteBomBulkCalculateRequest: finishingType validation 추가 | ⏳ | SUS/EGI | | 2.4 | KyungdongItemSeeder 수정 (향후 시딩용) | ⏳ | FG-코드 생성 로직 | ### 2.3 Phase 3: React 프론트엔드 | # | 작업 항목 | 상태 | 비고 | |---|----------|:----:|------| | 3.1 | LocationDetailPanel: 마감재질 Select UI 추가 | ⏳ | SUS/EGI 선택 | | 3.2 | LocationListPanel: 마감재질 컬럼/폼필드 추가 | ⏳ | 위치 추가 시 | | 3.3 | types.ts: QuoteLocation에 finishingType 추가 | ⏳ | | | 3.4 | actions.ts: BOM 산출 요청에 finishingType 포함 | ⏳ | | | 3.5 | QuoteRegistration.tsx: mock 데이터 업데이트 | ⏳ | | | 3.6 | QuoteSummaryPanel/PreviewContent: 마감재질 표시 | ⏳ | | ### 2.4 Phase 4: 검증 | # | 작업 항목 | 상태 | 비고 | |---|----------|:----:|------| | 4.1 | 통합 전후 BOM 계산 결과 비교 테스트 | ⏳ | 동일 입력 → 동일 결과 | | 4.2 | 견적 등록 → 산출 → 저장 E2E 테스트 | ⏳ | | --- ## 3. 작업 절차 ### 3.1 단계별 절차 ``` Step 1: DB 마이그레이션 스크립트 작성 ├── 6개 모델별 대표 FG 품목 선정 (유지할 item_id 결정) ├── BOM parent_item_id를 대표 item_id로 재매핑 ├── 대표 품목의 code를 통합 코드로 변경 (KWE01 등) ├── 대표 품목의 attributes에서 guiderail_type/finishing_type 제거 └── 나머지 12개 FG 품목 soft delete Step 2: API 수정 ├── FormRequest에 finishingType/FT validation 추가 ├── FormulaEvaluatorService에 FT → finishing_type 매핑 추가 ├── MapItemsToProcesses globalExcludes → item_type 기반 변경 └── KyungdongItemSeeder 코드 생성 로직 수정 Step 3: React 프론트엔드 ├── types.ts에 finishingType 필드 추가 ├── LocationDetailPanel에 마감재질 Select 추가 ├── LocationListPanel에 마감재질 폼필드/컬럼 추가 ├── actions.ts BOM 산출 요청에 finishingType 포함 └── Summary/Preview에 마감재질 표시 Step 4: 검증 ├── 동일 입력(KWE01 + wall + SUS)으로 기존 결과와 비교 ├── 모든 조합 테스트 (6모델 × 3설치 × 2마감) └── 견적 등록 → 산출 → 저장 E2E ``` --- ## 4. 상세 작업 내용 (코드 스니펫 포함) ### 4.1 현재 FG 품목 현황 (tenant_id=287) | 모델 | 벽면형-SUS | 벽면형-EGI | 측면형-SUS | 측면형-EGI | 통합 코드 | item_category | |------|-----------|-----------|-----------|-----------|----------|:------------:| | KWE01 | FG-KWE01-벽면형-SUS | FG-KWE01-벽면형-EGI | FG-KWE01-측면형-SUS | FG-KWE01-측면형-EGI | **KWE01** | SCREEN | | KWE02 | (동일 패턴) | | | | **KWE02** | SCREEN | | KWE03 | | | | | **KWE03** | SCREEN | | KWS01 | | | | | **KWS01** | STEEL | | KWS02 | | | | | **KWS02** | STEEL | | KWS03 | | | | | **KWS03** | STEEL | > KWE = 스크린(SCREEN), KWS = 철재(STEEL). item_category는 유지됨 (계산 분기에 사용) FG 코드 생성 원본 (`api/database/seeders/Kyungdong/KyungdongItemSeeder.php:305-307`): ```php $finishingShort = self::FINISHING_MAP[$model->finishing_type] ?? 'STD'; $code = "FG-{$model->model_name}-{$model->guiderail_type}-{$finishingShort}"; $name = "{$model->model_name} {$model->major_category} {$model->finishing_type} {$model->guiderail_type}"; ``` FINISHING_MAP (`KyungdongItemSeeder.php:39-42`): ```php private const FINISHING_MAP = [ 'SUS마감' => 'SUS', 'EGI마감' => 'EGI', ]; ``` items.attributes 구조: ```json { "model_name": "KWE01", "major_category": "스크린", "finishing_type": "SUS마감", "guiderail_type": "벽면형", "legacy_source": "models", "legacy_model_id": 123 } ``` ### 4.2 BOM 재매핑 전략 BOM은 FG 품목(parent)의 `items.bom` JSON 컬럼에 저장: ```json [ { "child_item_id": 123, "quantity": 1 }, { "child_item_id": 456, "quantity": 2 } ] ``` 마이그레이션 SQL 전략: ```sql -- Step 1: 모델별 대표 FG 품목 선정 (벽면형-SUS를 대표로) -- 대표 선정 기준: 같은 model_name 중 가장 작은 id -- Step 2: 대표 품목의 code 변경 UPDATE items SET code = 'KWE01' WHERE id = (대표_item_id) AND tenant_id = 287; -- Step 3: 대표 품목의 attributes에서 guiderail_type/finishing_type 제거 -- (이 속성들은 더 이상 품목 고유 속성이 아님) -- Step 4: 비대표 품목의 BOM을 대표 품목으로 이관 -- (동일 모델의 BOM은 동일하므로, BOM이 있는 품목의 bom을 대표로 복사) -- Step 5: 비대표 12개 품목 soft delete UPDATE items SET deleted_at = NOW(), deleted_by = 1 WHERE tenant_id = 287 AND item_type = 'FG' AND id NOT IN (대표_item_ids); ``` 핵심 안전 요소: - BOM의 `child_item_id`는 PT/SM 품목 → FG 통합과 **무관** - `FormulaEvaluatorService::getItemDetails()` (line 1110-1112)에서 `->where('code', $itemCode)` 조회 - 통합 후 code가 'KWE01'이 되면 `getItemDetails('KWE01')`로 정상 조회 ### 4.3 API 파라미터 흐름 (통합 후) ``` Frontend (LocationDetailPanel) ├── productCode: "KWE01" (통합 코드) ├── guideRailType: "wall" | "floor" | "mixed" ├── finishingType: "SUS" | "EGI" ← 새로 추가 └── motorPower: "single" | "three" ↓ actions.ts::calculateBomBulk() - POST /api/v1/quotes/calculate/bom/bulk body: { items: [{ finished_goods_code, openWidth, openHeight, guideRailType, motorPower, finishingType, ... }] } ↓ QuoteBomBulkCalculateRequest::normalizeInputVariables() (line 122-135) ├── 'W0' => openWidth, 'H0' => openHeight ├── 'GT' => guideRailType, 'MP' => motorPower └── 'FT' => finishingType ← 새로 추가 ↓ FormulaEvaluatorService::calculateKyungdongBom() (line 1574~) ├── getItemDetails("KWE01", tenantId) → items.code = "KWE01" 조회 (line 1110-1112) ├── $finishingType: FT → SUS/EGI ← 기존 line 1677 수정 ├── $installationType: GT → 벽면형/측면형/혼합형 (line 1680-1684) └── $motorVoltage: MP → 220V/380V (line 1687-1690) ↓ $calculatedVariables = array_merge() (line 1692-1708) 'finishing_type' => $finishingType (line 1705) ← 이미 포함됨 ↓ KyungdongFormulaHandler (변경 없음) ├── calculateSteelItems() line 458: $rawFinish = $params['finishing_type'] ?? 'SUS' ├── calculateGuideRails() line 540: $finishingType 파라미터 └── getBottomBarPrice() line 561: $finishingType 파라미터 ``` ### 4.4 핵심 파일별 변경 상세 --- #### 4.4.1 `api/app/Services/Quote/FormulaEvaluatorService.php` **현재 코드 (line 1676-1677):** ```php $productModel = $inputVariables['product_model'] ?? 'KSS01'; $finishingType = $inputVariables['finishing_type'] ?? 'SUS'; ``` **수정 후:** ```php $productModel = $inputVariables['product_model'] ?? 'KSS01'; // 마감재질: 프론트 FT(SUS/EGI) → finishing_type 매핑 $finishingType = $inputVariables['finishing_type'] ?? match ($inputVariables['FT'] ?? 'SUS') { 'EGI' => 'EGI', default => 'SUS', }; ``` > `$calculatedVariables` array_merge (line 1705)에는 이미 `'finishing_type' => $finishingType` 포함됨 --- #### 4.4.2 `api/app/Http/Requests/Quote/QuoteBomCalculateRequest.php` **현재 rules() (line 20-39)에 추가:** ```php // 기존 'GT' => 'nullable|string|in:wall,ceiling,floor,mixed', 'MP' => 'nullable|string|in:single,three', // 추가 'FT' => 'nullable|string|in:SUS,EGI', ``` **현재 getInputVariables() (line 74-89)에 추가:** ```php // 기존 'MP' => $validated['MP'] ?? 'single', // 추가 'FT' => $validated['FT'] ?? 'SUS', ``` --- #### 4.4.3 `api/app/Http/Requests/Quote/QuoteBomBulkCalculateRequest.php` **rules() (line 21-54)에 추가:** ```php // React 필드명 (camelCase) 'items.*.finishingType' => 'nullable|string|in:SUS,EGI', // API 변수명 (약어) 'items.*.FT' => 'nullable|string|in:SUS,EGI', ``` **normalizeInputVariables() (line 122-135)에 추가:** ```php // 기존 'MP' => $item['motorPower'] ?? $item['MP'] ?? 'single', // 추가 'FT' => $item['finishingType'] ?? $item['FT'] ?? 'SUS', ``` --- #### 4.4.4 `api/app/Console/Commands/MapItemsToProcesses.php` **현재 (line 48):** ```php private array $globalExcludes = ['FG-%', 'RM-%', 'EST-INSPECTION']; ``` **수정 후:** ```php private array $globalExcludes = ['RM-%', 'EST-INSPECTION']; // FG 제외는 item_type 기반으로 처리 (아래 쿼리에서 ->where('item_type', '!=', 'FG') 추가) ``` > 해당 명령어에서 items 조회 시 `->whereNotIn('item_type', ['FG'])` 조건 추가 --- #### 4.4.5 `api/database/seeders/Kyungdong/KyungdongItemSeeder.php` **현재 (line 305-307):** ```php $finishingShort = self::FINISHING_MAP[$model->finishing_type] ?? 'STD'; $code = "FG-{$model->model_name}-{$model->guiderail_type}-{$finishingShort}"; $name = "{$model->model_name} {$model->major_category} {$model->finishing_type} {$model->guiderail_type}"; ``` **수정 후:** ```php $code = $model->model_name; // KWE01, KWS01 등 $name = "{$model->model_name} {$model->major_category}"; ``` > 중복 방지: 같은 model_name은 하나만 생성 (기존: 설치유형×마감재질 조합별 생성 → 모델별 1개) --- #### 4.4.6 `react/src/components/quotes/types.ts` **LocationItem 인터페이스 (line 664-686)에 추가:** ```typescript export interface LocationItem { // ... 기존 필드 guideRailType: string; // 가이드레일 설치 유형 finishingType: string; // 마감재질 (SUS/EGI) ← 추가 motorPower: string; // 모터 전원 // ... } ``` --- #### 4.4.7 `react/src/components/quotes/actions.ts` **BomCalculateItem 인터페이스 (line 343-354)에 추가:** ```typescript export interface BomCalculateItem { finished_goods_code: string; openWidth: number; openHeight: number; quantity?: number; guideRailType?: string; finishingType?: string; // ← 추가 motorPower?: string; controller?: string; wingSize?: number; inspectionFee?: number; } ``` --- #### 4.4.8 `react/src/components/quotes/LocationDetailPanel.tsx` **상수 추가 (line 75 뒤):** ```typescript // 마감재질 const FINISHING_TYPES = [ { value: "SUS", label: "SUS (스테인리스)" }, { value: "EGI", label: "EGI (아연도금)" }, ]; ``` **2행 그리드 변경 (line 358-423):** 현재 `grid-cols-3` (가이드레일, 전원, 제어기) → `grid-cols-4`로 변경하고 마감재질 Select 추가: ```tsx {/* 2행: 가이드레일, 마감재질, 전원, 제어기 */}
{/* 가이드레일 (기존) */}
...
{/* 마감재질 (새로 추가) */}
{/* 전원 (기존) */}
...
{/* 제어기 (기존) */}
...
``` --- #### 4.4.9 `react/src/components/quotes/LocationListPanel.tsx` **formData 초기값 (line 110-120)에 추가:** ```typescript const [formData, setFormData] = useState({ // ... 기존 guideRailType: "wall", finishingType: "SUS", // ← 추가 motorPower: "single", // ... }); ``` **2행 폼 (line ~380 이후)에 마감재질 Select 추가** (가이드레일 Select 패턴과 동일) --- #### 4.4.10 `react/src/components/quotes/QuoteRegistration.tsx` **BOM 계산 페이로드 (line 459-469)에 finishingType 추가:** ```typescript const bomItem = { finished_goods_code: newLocation.productCode, openWidth: newLocation.openWidth, openHeight: newLocation.openHeight, quantity: newLocation.quantity, guideRailType: newLocation.guideRailType, finishingType: newLocation.finishingType, // ← 추가 motorPower: newLocation.motorPower, controller: newLocation.controller, wingSize: newLocation.wingSize, inspectionFee: newLocation.inspectionFee, }; ``` **다건 산출 (line 594-606)도 동일하게 finishingType 추가:** ```typescript const bomItems = formData.locations.map((loc) => ({ finished_goods_code: loc.productCode, // ... finishingType: loc.finishingType, // ← 추가 // ... })); ``` **기본값 (line 117):** ```typescript // 기존 guideRailType: "wall", // 추가 finishingType: "SUS", ``` **mock 데이터 (line 248):** ```typescript // 기존: productCode: randomProduct?.item_code || "FG-SCR-001" // 수정: productCode: randomProduct?.item_code || "KWE01" ``` --- #### 4.4.11 `react/src/components/quotes/QuoteSummaryPanel.tsx` & `QuotePreviewContent.tsx` 위치 정보 표시 영역에 마감재질 추가: ```typescript // QuoteSummaryPanel.tsx line 172 근처 {loc.productCode} ({loc.finishingType}) × {loc.quantity} // QuotePreviewContent.tsx line 209 근처 {loc.productCode} {loc.finishingType} // 또는 기존 컬럼에 병합 ``` --- #### 4.4.12 `react/src/app/[locale]/(protected)/sales/quote-management/[id]/page.tsx` **기존 견적 조회 시 BOM 재계산 페이로드 (line 60-70):** ```typescript const bomItems: BomCalculateItem[] = locationsNeedingRecalc.map(loc => ({ finished_goods_code: loc.productCode, openWidth: loc.openWidth, openHeight: loc.openHeight, quantity: loc.quantity, guideRailType: loc.guideRailType, // finishingType: loc.finishingType, ← 추가 필요 motorPower: loc.motorPower, controller: loc.controller, wingSize: loc.wingSize, inspectionFee: loc.inspectionFee, })); ``` ### 4.5 DB 마이그레이션 사전 검증 쿼리 마이그레이션 실행 전 반드시 확인할 쿼리: ```sql -- 1. 현재 FG 품목 전체 목록 확인 SELECT id, code, name, item_category, JSON_EXTRACT(attributes, '$.model_name') as model_name, JSON_EXTRACT(attributes, '$.guiderail_type') as guiderail_type, JSON_EXTRACT(attributes, '$.finishing_type') as finishing_type, bom IS NOT NULL AND bom != '[]' as has_bom FROM items WHERE tenant_id = 287 AND item_type = 'FG' AND deleted_at IS NULL ORDER BY code; -- 2. 모델별 BOM 동일성 검증 (같은 model_name의 bom이 동일한지) SELECT JSON_EXTRACT(attributes, '$.model_name') as model_name, COUNT(DISTINCT bom) as distinct_bom_count, COUNT(*) as total_count FROM items WHERE tenant_id = 287 AND item_type = 'FG' AND deleted_at IS NULL GROUP BY JSON_EXTRACT(attributes, '$.model_name'); -- distinct_bom_count = 1 이면 안전 (동일 모델의 BOM이 같음) -- 3. 다른 테이블에서 FG item_id 참조 확인 SELECT 'quote_items' as tbl, COUNT(*) as cnt FROM quote_items WHERE item_id IN ( SELECT id FROM items WHERE tenant_id = 287 AND item_type = 'FG' ) UNION ALL SELECT 'work_order_items', COUNT(*) FROM work_order_items WHERE item_id IN ( SELECT id FROM items WHERE tenant_id = 287 AND item_type = 'FG' ); -- 모두 0이면 안전하게 통합 가능 ``` --- ### 4.6 핵심 API 메서드 참조 (읽기 전용) 아래 메서드들은 **변경하지 않지만** 동작을 이해하기 위해 참조: **`FormulaEvaluatorService::getItemDetails()` (line 1102-1134):** ```php public function getItemDetails(string $itemCode, ?int $tenantId = null): ?array { $item = DB::table('items') ->where('tenant_id', $tenantId) ->where('code', $itemCode) // ← 여기서 code로 조회 ->whereNull('deleted_at') ->first(); // ... id, code, name, item_type, item_category, bom 등 반환 } ``` → 통합 후 `getItemDetails('KWE01')` 호출 시 code='KWE01' 품목 정상 조회 **`FormulaEvaluatorService::calculateKyungdongBom()` 핵심 흐름 (line 1574~):** ``` 1. getItemDetails($finishedGoodsCode) → 완제품 조회 2. $productCategory = $finishedGoods['item_category'] → 'SCREEN' 또는 'STEEL' 3. $productModel, $finishingType, $installationType, $motorVoltage 결정 4. $calculatedVariables = array_merge($inputVariables, [...]) 5. KyungdongFormulaHandler::calculateDynamicItems($calculatedVariables) 호출 ``` → `item_category`는 items 레코드에서 가져오므로 통합 후에도 정상 (KWE01 → SCREEN) **`KyungdongFormulaHandler` finishing_type 사용처:** - `calculateSteelItems()` line 458: `$rawFinish = $params['finishing_type'] ?? 'SUS'` - `calculateGuideRails()` line 540: 파라미터로 수신 - `getBottomBarPrice()` line 561: 가격 조회에 사용 - `getGuideRailPrice()` line 696: 가격 조회에 사용 → 모두 `$calculatedVariables['finishing_type']`에서 값을 가져오므로 매핑만 추가하면 됨 **React `getFinishedGoods()` (actions.ts line 302-317):** ```typescript const result = await executeServerAction({ url: buildApiUrl('/api/v1/items', { item_type: 'FG', has_bom: '1', size: '5000', }), }); ``` → `item_type='FG'`로 조회하므로 code 변경 영향 없음. 통합 후 6개만 반환됨. --- ## 5. 컨펌 대기 목록 | # | 항목 | 변경 내용 | 영향 범위 | 상태 | |---|------|----------|----------|------| | 1 | FG 품목 통합 마이그레이션 | 18개 → 6개, BOM 재매핑 | DB, 모든 FG 참조 | ⏳ 대기 | | 2 | 12개 FG 품목 soft delete | 통합 후 불필요 품목 삭제 | DB | ⏳ 대기 | --- ## 6. 변경 이력 | 날짜 | 항목 | 변경 내용 | 파일 | 승인 | |------|------|----------|------|------| | 2026-02-19 | - | 문서 초안 작성 | - | - | | 2026-02-19 | 혼합형 지원 | GT validation에 mixed 추가 | QuoteBomCalculateRequest, QuoteBomBulkCalculateRequest | ✅ | | 2026-02-19 | 모터 전압 | MP → motor_voltage 매핑 추가 | FormulaEvaluatorService | ✅ | | 2026-02-19 | 가이드레일 | GT → installation_type 매핑 추가 | FormulaEvaluatorService | ✅ | | 2026-02-19 | 혼합형 UI | GUIDE_RAIL_TYPES에 mixed 옵션 추가 | LocationDetailPanel | ✅ | --- ## 7. 참고 문서 - **품목 정책**: `docs/rules/item-policy.md` - **견적 시스템**: `docs/features/quotes/README.md` - **DB 스키마**: `docs/specs/database-schema.md` - **품질 체크리스트**: `docs/standards/quality-checklist.md` - **빠른 시작**: `docs/quickstart/quick-start.md` - **견적 계산 계획**: `docs/dev_plans/kd-quote-logic-plan.md` - **경동 품목 시더**: `api/database/seeders/Kyungdong/KyungdongItemSeeder.php` --- ## 8. 세션 및 메모리 관리 정책 ### 8.1 세션 시작 시 ``` read_memory("fg-consolidation-state") read_memory("fg-consolidation-snapshot") 계획 문서 읽기 → docs/dev_plans/fg-code-consolidation-plan.md ``` ### 8.2 작업 중 관리 | 컨텍스트 잔량 | Action | 내용 | |--------------|--------|------| | **30% 이하** | Snapshot | `write_memory("fg-consolidation-snapshot", ...)` | | **20% 이하** | Symbol Tracking | `write_memory("fg-consolidation-active-symbols", ...)` | | **10% 이하** | Stop & Save | 최종 상태 저장 후 세션 교체 권고 | ### 8.3 Serena 메모리 구조 - `fg-consolidation-state`: { phase, progress, next_step, last_decision } - `fg-consolidation-snapshot`: 코드 변경점 + 논의 요약 - `fg-consolidation-rules`: 불변 규칙 (코어 로직 변경 없음, BOM FK 안전 등) - `fg-consolidation-active-symbols`: 수정 중인 파일/심볼 리스트 --- ## 9. 검증 결과 ### 9.1 테스트 케이스 | 입력값 | 예상 결과 | 실제 결과 | 상태 | |--------|----------|----------|------| | KWE01 + wall + SUS + W0=2000 + H0=3000 | FG-KWE01-벽면형-SUS 동일 결과 | - | ⏳ | | KWE01 + floor + EGI + W0=2000 + H0=3000 | FG-KWE01-측면형-EGI 동일 결과 | - | ⏳ | | KWE01 + mixed + SUS + W0=2000 + H0=3000 | 혼합형 계산 정상 | - | ⏳ | | KWS01 + wall + SUS + W0=2000 + H0=3000 | FG-KWS01-벽면형-SUS 동일 결과 | - | ⏳ | | KWE01 + three + SUS + W0=5000 + H0=5000 | 삼상 모터 + SUS 정상 | - | ⏳ | ### 9.2 성공 기준 | 기준 | 달성 | 비고 | |------|------|------| | FG 품목 18개 → 6개 통합 | ⏳ | | | BOM 계산 결과 통합 전후 동일 | ⏳ | 모든 조합 | | 견적 등록 → 산출 → 저장 정상 | ⏳ | | | 마감재질 선택 UI 동작 | ⏳ | | | 기존 기능 회귀 없음 | ⏳ | | --- ## 10. 자기완결성 점검 결과 ### 10.1 체크리스트 검증 | # | 검증 항목 | 상태 | 비고 | |---|----------|:----:|------| | 1 | 작업 목적이 명확한가? | ✅ | 1.1 배경, 1.2 목표 | | 2 | 성공 기준이 정의되어 있는가? | ✅ | 9.2 성공 기준 | | 3 | 작업 범위가 구체적인가? | ✅ | 2. 대상 범위 13개 항목 | | 4 | 의존성이 명시되어 있는가? | ✅ | Phase 순서 = 의존성 | | 5 | 참고 파일 경로가 정확한가? | ✅ | 4.4 핵심 파일 변경 목록 | | 6 | 단계별 절차가 실행 가능한가? | ✅ | 3.1 + 4.x 상세 | | 7 | 검증 방법이 명시되어 있는가? | ✅ | 9.1 테스트 케이스 | | 8 | 모호한 표현이 없는가? | ✅ | 구체적 코드/파일명 명시 | ### 10.2 새 세션 시뮬레이션 테스트 | 질문 | 답변 가능 | 참조 섹션 | |------|:--------:|----------| | Q1. 이 작업의 목적은 무엇인가? | ✅ | 1.1, 1.2 | | Q2. 어디서부터 시작해야 하는가? | ✅ | 3.1 Step 1 | | Q3. 어떤 파일을 수정해야 하는가? | ✅ | 4.4 핵심 파일 변경 목록 | | Q4. 작업 완료 확인 방법은? | ✅ | 9.1, 9.2 | | Q5. 막혔을 때 참고 문서는? | ✅ | 7. 참고 문서 | **결과**: 5/5 통과 → ✅ 자기완결성 확보 --- ## 11. 리스크 및 롤백 ### 11.1 리스크 평가 | 리스크 | 확률 | 영향 | 대응 | |--------|:----:|:----:|------| | BOM parent_item_id 누락 | 중 | 높 | 마이그레이션 전 BOM 전수 검증 쿼리 실행 | | 견적 계산 결과 불일치 | 낮 | 높 | 통합 전후 동일 입력 비교 테스트 5건 이상 | | 기존 데이터 호환성 깨짐 | 낮 | 낮 | 현재 quote_items에 FG 코드 참조 데이터 없음 | | 프론트 productCode 참조 오류 | 중 | 중 | 46개 참조 지점 전수 확인 | ### 11.2 롤백 전략 - DB 마이그레이션은 Laravel down() 메서드로 롤백 가능하도록 작성 - 마이그레이션 실행 전 items + BOM 데이터 백업 쿼리 준비 - API/React 변경은 git revert로 원복 가능 --- *이 문서는 /sc:plan 스킬로 생성되었습니다.*