- 개발팀 전용 폴더 dev/ 생성 (standards, guides, quickstart, changes, deploys, data, history, dev_plans 이동) - 프론트엔드 전용 폴더 frontend/ 생성 (api/ → frontend/api-specs/) - 기획팀 폴더 requests/ 생성 - plans/ → dev/dev_plans/ 이름 변경 - README.md 신규 (사람용 안내), INDEX.md 재작성 (Claude Code용) - resources.md 신규 (노션 링크용, assets/brochure 이관 예정) - CURRENT_WORKS.md 삭제, TODO.md → dev/ 이동 - 전체 참조 경로 업데이트 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
26 KiB
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):
$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):
private const FINISHING_MAP = [
'SUS마감' => 'SUS',
'EGI마감' => 'EGI',
];
items.attributes 구조:
{
"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 컬럼에 저장:
[
{ "child_item_id": 123, "quantity": 1 },
{ "child_item_id": 456, "quantity": 2 }
]
마이그레이션 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):
$productModel = $inputVariables['product_model'] ?? 'KSS01';
$finishingType = $inputVariables['finishing_type'] ?? 'SUS';
수정 후:
$productModel = $inputVariables['product_model'] ?? 'KSS01';
// 마감재질: 프론트 FT(SUS/EGI) → finishing_type 매핑
$finishingType = $inputVariables['finishing_type'] ?? match ($inputVariables['FT'] ?? 'SUS') {
'EGI' => 'EGI',
default => 'SUS',
};
$calculatedVariablesarray_merge (line 1705)에는 이미'finishing_type' => $finishingType포함됨
4.4.2 api/app/Http/Requests/Quote/QuoteBomCalculateRequest.php
현재 rules() (line 20-39)에 추가:
// 기존
'GT' => 'nullable|string|in:wall,ceiling,floor,mixed',
'MP' => 'nullable|string|in:single,three',
// 추가
'FT' => 'nullable|string|in:SUS,EGI',
현재 getInputVariables() (line 74-89)에 추가:
// 기존
'MP' => $validated['MP'] ?? 'single',
// 추가
'FT' => $validated['FT'] ?? 'SUS',
4.4.3 api/app/Http/Requests/Quote/QuoteBomBulkCalculateRequest.php
rules() (line 21-54)에 추가:
// React 필드명 (camelCase)
'items.*.finishingType' => 'nullable|string|in:SUS,EGI',
// API 변수명 (약어)
'items.*.FT' => 'nullable|string|in:SUS,EGI',
normalizeInputVariables() (line 122-135)에 추가:
// 기존
'MP' => $item['motorPower'] ?? $item['MP'] ?? 'single',
// 추가
'FT' => $item['finishingType'] ?? $item['FT'] ?? 'SUS',
4.4.4 api/app/Console/Commands/MapItemsToProcesses.php
현재 (line 48):
private array $globalExcludes = ['FG-%', 'RM-%', 'EST-INSPECTION'];
수정 후:
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):
$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}";
수정 후:
$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)에 추가:
export interface LocationItem {
// ... 기존 필드
guideRailType: string; // 가이드레일 설치 유형
finishingType: string; // 마감재질 (SUS/EGI) ← 추가
motorPower: string; // 모터 전원
// ...
}
4.4.7 react/src/components/quotes/actions.ts
BomCalculateItem 인터페이스 (line 343-354)에 추가:
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 뒤):
// 마감재질
const FINISHING_TYPES = [
{ value: "SUS", label: "SUS (스테인리스)" },
{ value: "EGI", label: "EGI (아연도금)" },
];
2행 그리드 변경 (line 358-423):
현재 grid-cols-3 (가이드레일, 전원, 제어기) → grid-cols-4로 변경하고 마감재질 Select 추가:
{/* 2행: 가이드레일, 마감재질, 전원, 제어기 */}
<div className="grid grid-cols-4 gap-3">
{/* 가이드레일 (기존) */}
<div>...</div>
{/* 마감재질 (새로 추가) */}
<div>
<label className="text-xs text-gray-600 flex items-center gap-1">
🔩 마감재질
</label>
<Select
value={location.finishingType}
onValueChange={(value) => handleFieldChange("finishingType", value)}
disabled={disabled}
>
<SelectTrigger className="h-8 text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
{FINISHING_TYPES.map((type) => (
<SelectItem key={type.value} value={type.value}>
{type.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* 전원 (기존) */}
<div>...</div>
{/* 제어기 (기존) */}
<div>...</div>
</div>
4.4.9 react/src/components/quotes/LocationListPanel.tsx
formData 초기값 (line 110-120)에 추가:
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 추가:
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 추가:
const bomItems = formData.locations.map((loc) => ({
finished_goods_code: loc.productCode,
// ...
finishingType: loc.finishingType, // ← 추가
// ...
}));
기본값 (line 117):
// 기존
guideRailType: "wall",
// 추가
finishingType: "SUS",
mock 데이터 (line 248):
// 기존: productCode: randomProduct?.item_code || "FG-SCR-001"
// 수정: productCode: randomProduct?.item_code || "KWE01"
4.4.11 react/src/components/quotes/QuoteSummaryPanel.tsx & QuotePreviewContent.tsx
위치 정보 표시 영역에 마감재질 추가:
// QuoteSummaryPanel.tsx line 172 근처
{loc.productCode} ({loc.finishingType}) × {loc.quantity}
// QuotePreviewContent.tsx line 209 근처
<td>{loc.productCode}</td>
<td>{loc.finishingType}</td> // 또는 기존 컬럼에 병합
4.4.12 react/src/app/[locale]/(protected)/sales/quote-management/[id]/page.tsx
기존 견적 조회 시 BOM 재계산 페이로드 (line 60-70):
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 마이그레이션 사전 검증 쿼리
마이그레이션 실행 전 반드시 확인할 쿼리:
-- 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):
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):
const result = await executeServerAction<FGApiResponse>({
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 스킬로 생성되었습니다.