Files
sam-docs/dev/dev_plans/kd-quote-logic-plan.md
권혁성 db63fcff85 refactor: [docs] 팀별 폴더 구조 재편 (공유/개발/프론트/기획)
- 개발팀 전용 폴더 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>
2026-03-05 16:46:03 +09:00

36 KiB
Raw Permalink Blame History

경동기업 견적 로직 분석 및 구현 계획

작성일: 2026-01-28 목적: 5130 레거시 견적 시스템 분석 → SAM 동적 BOM/견적 로직 구현 선행 작업: kd-items-migration-plan.md (정적 품목/단가 완료) 상태: 🔄 Phase 0 진행중


🚀 Quick Start

이 문서의 목적

정적 품목 데이터는 이관 완료 (items 651건, prices 651건). 이제 동적으로 BOM을 계산하고 견적을 산출하는 로직을 5130에서 분석하여 SAM에 구현.

환경 정보

항목
레거시 소스 5130/ (프로젝트 루트)
대상 테넌트 287 (경동기업)
관련 SAM 페이지 https://dev.sam.kr/sales/quote-management/new

📍 현재 진행 상태

항목 내용
현재 단계 Phase 4: SAM 구현 완료
다음 작업 Phase 5: 통합 테스트 및 프론트엔드 연동
진행률 4/5 (100%) - Phase 0~4 완료
마지막 업데이트 2026-01-29

Phase 4.8 테스트 결과 (2026-01-29)

📊 테스트 입력값
- W0: 3000mm, H0: 2500mm, QTY: 1
- 철재형, 5인치 브라켓, 매립형 제어기
- KSS01 모델, SUS 마감

📦 계산된 항목 (16개)
1. 주자재(스크린) → 228,750원
2. 모터 400K → 150,000원
3. 제어기 매립형 → 45,000원
4. 케이스 → 45,000원
5. 케이스용 연기차단재 → 10,500원
6. 케이스 마구리 → 10,000원
7. 가이드레일 → 73,200원
8. 레일용 연기차단재 → 15,250원
9. 하장바 → 24,000원
10. L바 → 13,500원
11. 보강평철 → 9,000원
12. 무게평철12T → 24,000원
13. 환봉 → 8,000원
14. 감기샤프트 5인치 → 65,000원
15. 각파이프 → 12,000원
16. 앵글 앵글3T → 18,000원

💰 합계: 751,200원 ✅

1. 배경 및 문제 정의

1.1 현재 상황

SAM 견적 화면에서 FG-KSS01-벽면형-SUS 선택 시:

  • 현재: 3개 항목만 표시 (가이드레일, 하단마감재, L-BAR)
  • 기대: 본체, 절곡품, 모터/제어기, 부자재 등 전체 BOM

1.2 레거시 DB 구조 (분석 완료)

models (모델 마스터)
  └─ parts (대분류 부품: 가이드레일, 하단마감재)
       └─ parts_sub (세부 절곡품: 1번마감제, 2번본체, 3번-C, 4번-D...)

KSS01 벽면형 예시:

대분류 (parts) 세부품 (parts_sub) 재질 수량
가이드레일 1번(마감제) SUS 1.2T 1
2번(본체) EGI 1.55T 2
3번(벽면형-C) EGI 1.55T 1
4번(벽면형-D) EGI 1.55T 1
하단마감재 1번(하장바) SUS 1.5T 1

1.3 동적 항목 (5130 분석 필요)

항목 설명 레거시 소스
모터 W0, H0 기반 용량 자동 계산 5130 로직 분석 필요
제어기 모터 사양에 따라 연동 5130 로직 분석 필요
부자재 모델/규격별 자동 추가 5130 로직 분석 필요
절곡품 수량 파라미터 기반 동적 계산 5130 로직 분석 필요

2. 분석 대상

2.1 5130 디렉토리 구조 (분석 완료)

5130/
├── estimate/              # 견적 관련 (핵심 분석 대상)
│   ├── README.md          # 시스템 문서
│   ├── estimate.php       # 스크린 견적 메인 페이지
│   ├── slat.php           # 철재 견적 메인 페이지
│   ├── get_screen_amount.php  # 스크린 금액 계산 엔진 ⭐
│   ├── get_slat_amount.php    # 철재 금액 계산 엔진 ⭐
│   ├── fetch_unitprice.php    # 단가 조회 유틸리티 ⭐
│   ├── write_form.php         # 견적서 양식 생성
│   └── common/
│       └── calculation.js     # 프론트엔드 계산 로직
├── output/                # 출력/리포트
├── dbeditor/              # DB 관리
└── [기타 모듈]/

2.2 분석 우선순위

순위 대상 목적
1 견적 생성 로직 BOM 자동 구성 방식 파악
2 모터 계산 로직 W0/H0 → 모터 용량 공식
3 절곡품 계산 로직 파라미터 → 수량/단가 공식
4 부자재 추가 로직 모델별 자동 추가 규칙
5 가격 산출 로직 최종 견적 금액 계산

3. 작업 계획

Phase 0: 5130 탐색 및 구조 파악

  • 5130/ 디렉토리 구조 분석
  • 견적 관련 파일 식별 (estimate/, output/)
  • 주요 함수/클래스 목록화 (아래 섹션 4.3 참조)

Phase 1: 견적 생성 로직 분석 🔄

  • 모델 선택 → BOM 구성 흐름 파악
  • 동적 항목 추가 조건 분석 (체크박스 기반)
  • DB 조회 패턴 파악 (BDmodels, price_* 테이블)
  • 세부 계산 로직 문서화

Phase 2: 계산 공식 추출

  • 모터 용량 계산 공식 (calculateMotorSpec 분석 완료)
  • 절곡품 수량/단가 계산 공식 (섹션 4.12 참조)
  • 부자재 자동 추가 규칙 (섹션 4.13 참조)

Phase 3: SAM 설계

  • 기존 견적 시스템 분석 (QuoteCalculationService, FormulaEvaluatorService)
  • 5130 로직 통합 설계 → 하이브리드 접근 결정 (섹션 10.1)
  • API 엔드포인트 확장 설계 → 기존 엔드포인트 활용
  • DB 스키마 변경 필요 여부 → kd_price_tables 신규 테이블 (옵션)

Phase 4: SAM 구현 🔄

  • 4.1 KyungdongFormulaHandler 클래스 생성 (경동 전용)
  • 4.2 FormulaEvaluatorService 확장 (tenant 분기)
  • 4.3 모터 용량 계산 구현
  • 4.4 kd_price_tables 마이그레이션 + Model 생성
  • 4.5 price_* 테이블 조회 로직 구현 (KdPriceTable 연동)
  • 4.6 단가 데이터 마이그레이션 (Seeder)
  • 4.7 절곡품 계산 구현 (10종)
  • 4.8 API 테스트 및 검증

Phase 5: 검증

  • 레거시 vs SAM 결과 비교
  • 사용자 테스트
  • 배포

4. 레거시 분석 기록

4.1 분석된 테이블

테이블 용도 분석 상태
models 모델 마스터 완료
parts 대분류 부품 완료
parts_sub 세부 절곡품 완료
BDmodels BOM + 단가 JSON 완료
price_motor 모터 단가 완료
price_shaft 샤프트 계산 참조 완료
price_pipe 파이프 계산 참조 완료
price_raw_materials 원자재 단가 완료

4.2 분석된 5130 코드

파일/모듈 내용 분석 상태
estimate/README.md 시스템 문서 완료
estimate/estimate.php 스크린 견적 메인 완료
estimate/get_screen_amount.php 스크린 금액 계산 엔진 완료
estimate/get_slat_amount.php 철재 금액 계산 엔진 완료
estimate/fetch_unitprice.php 단가 조회 유틸리티 완료
estimate/common/calculation.js 프론트엔드 계산 완료

4.3 핵심 함수 목록

금액 계산 함수

함수명 파일 역할
calculateScreenAmount() get_screen_amount.php 스크린 견적 총액 계산
calculateSlatAmount() get_slat_amount.php 철재 견적 총액 계산
calculateGuideRailPrice() get_screen_amount.php 가이드레일 단가 계산
calculateShaftPrice() get_screen_amount.php 감기샤프트 단가 계산

단가 조회 함수 (fetch_unitprice.php)

함수명 역할 참조 테이블
searchBracketSize() 모터 중량 → 브라켓 크기 -
calculateMotorSpec() 중량/인치 → 모터 용량 (150K~1000K) -
getPriceForMotor() 모터 용량 → 단가 조회 price_motor
calculateControllerSpec() 제어기 타입 → 단가 조회 price_motor
calculatePipe() 파이프 규격 → 단가 조회 price_pipe
calculateShaft() 샤프트 규격 → 단가 조회 price_shaft
calculateAngle() 앵글 규격 → 단가 조회 price_angle
slatPrice() 원자재 → 단가 조회 price_raw_materials

4.4 모터 용량 계산 공식 (추출 완료)

모터 용량 = f(제품타입, 중량, 브라켓인치)

┌──────────┬─────────┬──────────────────────────────────┐
│ 제품타입  │ 인치    │ 중량 범위 → 용량                  │
├──────────┼─────────┼──────────────────────────────────┤
│ 스크린    │ 4"      │ ≤150kg → 150K                    │
│          │         │ 150~300kg → 300K                 │
│          │         │ 300~400kg → 400K                 │
├──────────┼─────────┼──────────────────────────────────┤
│ 스크린    │ 5"      │ ≤123kg → 150K                    │
│          │         │ 123~246kg → 300K                 │
│          │         │ 246~327kg → 400K                 │
│          │         │ 327~500kg → 500K                 │
│          │         │ 500~600kg → 600K                 │
├──────────┼─────────┼──────────────────────────────────┤
│ 스크린    │ 6"      │ ≤104kg → 150K                    │
│          │         │ 104~208kg → 300K                 │
│          │         │ 208~300kg → 400K                 │
│          │         │ 300~424kg → 500K                 │
│          │         │ 424~508kg → 600K                 │
├──────────┼─────────┼──────────────────────────────────┤
│ 철재      │ 4"      │ ≤300kg → 300K                    │
│          │         │ 300~400kg → 400K                 │
├──────────┼─────────┼──────────────────────────────────┤
│ 철재      │ 5"      │ ≤246kg → 300K                    │
│          │         │ 246~327kg → 400K                 │
│          │         │ 327~500kg → 500K                 │
│          │         │ 500~600kg → 600K                 │
├──────────┼─────────┼──────────────────────────────────┤
│ 철재      │ 6"      │ ≤208kg → 300K                    │
│          │         │ 208~277kg → 400K                 │
│          │         │ 277~424kg → 500K                 │
│          │         │ 424~508kg → 600K                 │
│          │         │ 508~800kg → 800K                 │
│          │         │ 800~1000kg → 1000K               │
├──────────┼─────────┼──────────────────────────────────┤
│ 철재      │ 8"      │ ≤324kg → 500K                    │
│          │         │ 324~388kg → 600K                 │
│          │         │ 388~611kg → 800K                 │
│          │         │ 611~1000kg → 1000K               │
└──────────┴─────────┴──────────────────────────────────┘

4.5 견적 항목 구성 (스크린)

체크박스 옵션에 따라 동적으로 항목 포함/제외:

체크박스 포함 항목
slatcheck (주자재) 주자재(스크린), 환봉
steel (절곡) 케이스, 가이드레일, 하장바, L바, 보강평철, 연기차단재(케이스/레일), 케이스 마구리
motor (모터) 모터 (경동견적가포함일 때만)
partscheck (부자재) 감기샤프트, 무게평철12T, 각파이프, 앵글
warranty (보증) (금액 조정에 영향)

4.6 가격 산출 흐름

1. 검사비 (고정)
2. 주자재 = 원자재단가 × 면적(W×H/1000000)
3. 모터 = 용량별 단가표 조회
4. 제어기 = 매립/노출/뒷박스 × 수량
5. 케이스 = 규격별 단가 × 길이(m)
6. 가이드레일 = 모델|마감재|규격별 단가 × 길이(m) × 2
7. 하장바/L바 = 단가 × 길이(m)
8. 샤프트 = 규격별(3",4",5") × 길이별(3000~8200) 단가표
9. 파이프 = 두께(1.4) × 길이(3000/6000) 단가표
10. 앵글 = 타입(3T/4T) × 두께(2.5) × 수량

5. 기술적 고려사항

5.1 SAM 아키텍처 준수

// Service-First 패턴
class QuoteBomService extends Service
{
    public function calculateDynamicBom(int $modelId, array $parameters): array
    {
        // 1. 정적 BOM 조회 (items.bom)
        // 2. 파라미터 기반 동적 항목 계산
        // 3. 모터/제어기 자동 추가
        // 4. 부자재 자동 추가
        // 5. 단가 계산
    }
}

5.2 API 설계 (예상)

POST /api/v1/quotes/calculate-bom
Request:
{
    "model_id": 13147,  // FG-KSS01-벽면형-SUS
    "parameters": {
        "W0": 3000,     // 폭
        "H0": 2000,     // 높이
        "installation_type": "벽면형",
        "power_source": "220V"
    }
}

Response:
{
    "static_bom": [...],      // 기존 items.bom
    "dynamic_items": [...],   // 모터, 제어기, 부자재
    "calculated_values": {
        "motor_capacity": "150K",
        "total_area": 6.0,
        "estimated_weight": 45.5
    },
    "pricing": {...}
}

6. 관련 문서


7. 변경 이력

날짜 항목 변경 내용
2026-01-28 문서 생성 초기 계획 수립, 레거시 DB 분석 결과 반영
2026-01-28 Phase 0 완료 5130 estimate 디렉토리 분석 완료, 핵심 함수 목록화
2026-01-28 Phase 1-2 진행 모터 용량 계산 공식 추출, 가격 산출 흐름 문서화
2026-01-28 Phase 2 계속 브라켓 크기 공식, BDmodels 구조, SAM 매핑 전략 추가
2026-01-28 Phase 3 시작 기존 SAM 견적 시스템 분석, 5130 통합 설계 문서화
2026-01-29 설계 결정 체크박스 방식 → "전체계산 → 개별제거" 방식으로 변경
2026-01-29 Phase 2 완료 절곡품/부자재/주자재 계산 공식 추출 완료 (4.12~4.15)
2026-01-29 Phase 4 설계 하이브리드 접근 결정 (범용 + 경동전용 Handler), 구현 계획 수립
2026-01-29 Phase 4.1 완료 KyungdongFormulaHandler 기본 구조 생성 (모터/브라켓 계산)
2026-01-29 Phase 4.2 완료 FormulaEvaluatorService 확장 (tenant_id=287 분기 처리)
2026-01-29 Phase 4.3~4.5 완료 kd_price_tables 마이그레이션, KdPriceTable 모델, Seeder, 단가 조회 연동
2026-01-29 Phase 4.6 완료 부자재 계산 (3종: 샤프트, 파이프, 앵글) 구현

4.7 브라켓 크기 결정 공식 (추출 완료)

searchBracketSize(중량, 인치) → 브라켓크기

┌──────────┬──────────────────────────────────┐
│ 모터용량  │ 브라켓 사이즈                     │
├──────────┼──────────────────────────────────┤
│ 300K     │ 530*320                          │
│ 400K     │ 530*320                          │
│ 500K     │ 600*350                          │
│ 600K     │ 600*350                          │
│ 800K     │ 690*390                          │
│ 1000K    │ 690*390                          │
└──────────┴──────────────────────────────────┘

[중량만으로 판단 (인치 없을 때)]
- ≤300kg → 300K
- ≤400kg → 400K
- ≤500kg → 500K
- ≤600kg → 600K
- ≤800kg → 800K
- ≤1000kg → 1000K

4.8 견적 입력 컬럼 매핑 (스크린)

컬럼 필드명 설명
col4 모델코드 KSS01, KWS01 등
col5 제목 현장명
col6 가이드레일타입 벽면형, 측면형, 혼합형
col7 마감재질 SUS, EGI 등
col10 폭(W) mm 단위
col11 높이(H) mm 단위
col14 수량 대수
col15~17 제어기 매립형/노출형/뒷박스 수량
col18_brand 모터업체 경동(견적가포함) 등
col19 모터용량 150K~1000K
col22 앵글사이즈 모터받침용
col23 가이드레일길이 mm 단위
col31~35 연기차단재 각 규격별 수량
col36 케이스규격 또는 col36_custom
col37 케이스길이 mm 단위
col45 마구리규격
col48 하장바길이 mm 단위
col49~50 하장바 수량 관련
col51 L바길이 mm 단위
col52~53 L바 수량 관련
col54 보강평철길이 mm 단위
col55~56 보강평철 수량 관련
col57 무게평철 수량
col59~65 샤프트 규격별(3"/4"/5") × 길이별
col68~69 각파이프 3000/6000 수량
col70 환봉 수량
col71 앵글 수량

4.9 단가 테이블 JSON 구조

price_shaft (샤프트)

  • col4: 사이즈 (3, 4, 5인치)
  • col10: 길이 (m 단위, 예: 3.0 = 3000mm)
  • col19: 판매가

price_pipe (각파이프)

  • col2: 길이 (3000, 6000)
  • col4: 두께 (1.4)
  • col8: 판매가

price_angle (앵글)

  • col2: 타입 (스크린용, 철재용)
  • col3: 브라켓크기 (530320, 600350, 690*390)
  • col4: 앵글타입 (앵글3T, 앵글4T)
  • col10: 두께 (2.5)
  • col19: 판매가

price_motor (모터/제어기)

  • col2: 용량/타입 (150K, 300K, 매립형, 노출형, 뒷박스)
  • col13: 판매가

price_raw_materials (원자재)

  • col2: 원자재명 (스크린, 슬랫, 조인트바 등)
  • col13: 판매가

4.10 BDmodels 테이블 구조 (절곡품 단가)

컬럼 구조:

컬럼 설명 예시
model_name 모델코드 KSS01, KWS01, KDSS01
seconditem 부품분류 케이스, 가이드레일, 하단마감재, L-BAR
spec 규격 12070, 650550
finishing_type 마감재질 SUS, EGI
unitprice 단가 원/m 또는 원/개

seconditem 종류:

  • 케이스: 케이스박스 (규격별 단가)
  • 가이드레일: 레일 (모델+마감+규격별 단가)
  • 하단마감재: 하장바 (모델+마감별 단가)
  • L-BAR: L바 (모델별 단가)
  • 보강평철: 평철 (공통 단가)
  • 마구리: 케이스 마감재 (규격별 단가)
  • 케이스용 연기차단재: (공통 단가)
  • 가이드레일용 연기차단재: (공통 단가)

단가 조회 키 패턴:

// 가이드레일: 모델코드|마감재질|규격
$key = "KSS01|SUS|120*70";
$price = $guidrailPrices[$key];

// 케이스: 규격만
$price = $shutterBoxprices["650*550"];

// 하단마감재: 모델코드 + 마감재질 매칭
if ($prodcode == $modelCode && $finishing == $load_finishingType) {
    $price = $bottomBarPrices;
}

4.11 SAM 매핑 전략

현재 SAM items 테이블의 BOM 구조:

// items.bom (JSON)
[
  {"child_item_id": 123, "quantity": 1},  // 가이드레일
  {"child_item_id": 456, "quantity": 1},  // 하단마감재
  {"child_item_id": 789, "quantity": 1}   // L-BAR
]

문제점:

  • 정적 BOM만 저장 (동적 계산 불가)
  • 모터/제어기/부자재 누락
  • 파라미터(W, H, 수량) 기반 수량 계산 없음

해결 방안 (Phase 3 설계 시 반영):

// 1. 정적 BOM + 동적 계산 분리
class QuoteBomService {
    public function calculate(int $modelId, array $params): array
    {
        // 1. 정적 BOM 조회 (items.bom)
        $staticBom = $this->getStaticBom($modelId);

        // 2. 동적 항목 계산
        $dynamicItems = $this->calculateDynamicItems($params);

        // 3. 단가 적용
        return $this->applyPricing($staticBom, $dynamicItems, $params);
    }

    private function calculateDynamicItems(array $params): array
    {
        $items = [];

        // 모터 (체크박스 옵션)
        if ($params['motor_check']) {
            $motorCapacity = $this->calculateMotorCapacity(
                $params['weight'],
                $params['bracket_inch']
            );
            $items['motor'] = $this->getMotorPrice($motorCapacity);
        }

        // 제어기
        $items['controller'] = $this->calculateController($params);

        // 샤프트/파이프/앵글 (부자재)
        if ($params['parts_check']) {
            $items['shaft'] = $this->calculateShaft($params);
            $items['pipe'] = $this->calculatePipe($params);
            $items['angle'] = $this->calculateAngle($params);
        }

        return $items;
    }
}

4.12 절곡품 계산 공식 (steel 체크박스)

절곡품 = BDmodels 테이블 조회 (seconditem별 단가)

품목 조회 키 계산식 비고
케이스 seconditem='케이스', spec=규격 단가/1000 × 길이(mm) × 수량 기본단가 500*380 기준 면적비 계산
케이스용 연기차단재 seconditem='케이스용 연기차단재' 단가 × 길이(m) × 수량
케이스 마구리 seconditem='마구리', spec=규격 단가 × 수량 col45 규격
가이드레일 model_name|finishing_type|spec 단가 × 길이(m) × 수량 벽면/측면 ×2, 혼합 각1
레일용 연기차단재 seconditem='가이드레일용 연기차단재' 단가 × 길이(m) × 2 × 수량
하장바 model_name, seconditem='하단마감재', finishing_type 단가 × 길이(m) × 수량 col48 길이
L바 model_name, seconditem='L-BAR' 단가 × 길이(m) × 수량 col51 길이
보강평철 seconditem='보강평철' 단가 × 길이(m) × 수량 col54 길이
무게평철12T 고정 12,000원 12,000 × col57(수량)
환봉 고정 2,000원 2,000 × col70(수량)

가이드레일 타입별 처리:

벽면형(120*70)  → baseKey|120*70 × 2개
측면형(120*100) → baseKey|120*100 × 2개
혼합형(120*70+120*100) → baseKey|120*70 + baseKey|120*100 (각 1개)

4.13 부자재 계산 공식 (partscheck 체크박스)

부자재 = price_ 테이블 조회 (JSON itemList)*

품목 테이블 조회 조건 계산식
감기샤프트 price_shaft col4=사이즈, col10=길이(m) col19(판매가) × 수량
각파이프 price_pipe col4=두께(1.4), col2=길이 col8(판매가) × 수량
앵글 price_angle col2='앵글3T', col10=두께(2.5) col19(판매가) × 수량

샤프트 규격별 컬럼 매핑:

col59 → 3" × 300mm (사실상 미사용)
col60 → 4" × 3000mm
col61 → 4" × 4500mm
col62 → 4" × 6000mm
col63 → 5" × 6000mm
col64 → 5" × 7000mm
col65 → 5" × 8200mm

각파이프 컬럼 매핑:

col68 → 1.4T × 3000mm 수량
col69 → 1.4T × 6000mm 수량

4.14 모터 받침용 앵글 (특수 조건)

조건: col22(앵글사이즈) 값이 있고,
      (slatcheck만 체크 AND motor/steel/partscheck 모두 미체크) 가 아닐 때

계산: calculateAngle(수량, itemList, '스크린용') × 수량 × 4

4.15 주자재 계산 공식 (slatcheck 체크박스)

스크린 가격 = 원자재단가 × 면적(㎡)

면적 = W × (H + 550) / 1,000,000
      ↑ 550mm는 스크린 기본 여유분 (350 + 200 추가)

원자재단가: price_raw_materials 테이블에서 col2='실리카' 조회 → col13(판매가)

8. 다음 작업 (TODO)

즉시 필요

  1. 브라켓 크기 결정 공식 완료
  2. BDmodels 조회 패턴 완료
  3. 절곡품 수량 계산 공식 - parts_sub 기반 동적 수량 결정 로직 (선택적)

SAM 구현 시 고려사항

  1. 전체 계산 → 개별 제거 방식: 5130의 체크박스 방식 대신, 전체 BOM 계산 후 불필요 항목 제거
  2. 단가 테이블 통합: price_motor, price_shaft, price_pipe 등 → SAM prices 테이블과 연동
  3. BOM 동적 생성 API: 파라미터(W0, H0) 입력 → 전체 견적 항목 반환 → UI에서 개별 제거

9. Phase 3: SAM 설계

9.1 기존 SAM 견적 시스템 분석

현재 구조

api/app/Services/Quote/
├── QuoteCalculationService.php   # 견적 계산 메인 서비스
├── FormulaEvaluatorService.php   # 수식 평가 엔진
├── QuoteService.php              # 견적 CRUD
└── Requests/
    └── QuoteBomCalculateRequest.php  # BOM 계산 입력값 검증

QuoteCalculationService 핵심 메서드

메서드 역할 입력 출력
calculate() 일반 견적 계산 inputs, productCategory, productId items, costs, errors
calculateBom() BOM 기반 견적 finishedGoodsCode, inputs, debug finished_goods, items, grand_total
calculateBomBulk() 다건 BOM 계산 inputItems[], debug summary, items[]
preview() 견적 미리보기 inputs, productCategory, productId (calculate와 동일)
recalculate() 기존 견적 재계산 Quote (calculate와 동일)

FormulaEvaluatorService 10단계 BOM 계산

Step 1: 입력값 수집 (W0, H0, QTY, PC, GT, MP, CT, WS, INSP)
Step 2: 완제품 선택 (finishedGoodsCode → Item 조회)
Step 3: 변수 계산 (수식 기반 중간값)
Step 4: BOM 전개 (items.bom JSON → 구성품 목록)
Step 5: 단가 출처 결정 (prices 테이블 or 수식 계산)
Step 6: 수량 수식 평가 (formula → 실제 수량)
Step 7: 단가 계산 (unit_price × quantity)
Step 8: 공정별 그룹화 (category 기준)
Step 9: 소계 계산 (그룹별 합계)
Step 10: 최종 합계 (grand_total)

현재 입력 파라미터 (QuoteBomCalculateRequest)

파라미터 설명 필수
W0 개구부 폭(mm)
H0 개구부 높이(mm)
QTY 수량
PC 제품코드
GT 가이드타입
MP 모터파워
CT 제어타입
WS 와이어사이드
INSP 검사비

9.2 5130 로직 통합 설계

5130 vs SAM 비교

항목 5130 SAM (현재) SAM (목표)
모터 계산 calculateMotorSpec() 없음 (수동 입력) 자동 계산
브라켓 크기 searchBracketSize() 없음 자동 계산
항목 선택 체크박스 (사전 선택) 없음 전체계산 → 개별제거
절곡품 단가 BDmodels 테이블 prices 테이블 prices + 범위 조회
부자재 파라미터 기반 동적 추가 정적 BOM 동적 계산

항목 선택 방식 변경 (중요)

5130 방식 (체크박스):

☑ 주자재  ☑ 절곡  ☐ 모터  ☑ 부자재  →  계산

SAM 방식 (전체계산 → 개별제거):

전체 BOM 계산 → 견적 라인 표시 → 불필요 항목 제거/수량 조정

┌─────────────────────────────────────────────────┐
│ 품목명          │ 수량 │ 단가   │ 금액    │ ⊘  │
├─────────────────────────────────────────────────┤
│ 스크린원단      │  6㎡ │ 15,000 │  90,000 │    │
│ 가이드레일      │  4m  │ 12,000 │  48,000 │    │
│ 모터 300K       │  1   │ 85,000 │  85,000 │ ✕  │ ← 제거 가능
│ 제어기 매립형    │  1   │ 25,000 │  25,000 │ ✕  │ ← 제거 가능
│ 감기샤프트 4"   │  1   │ 35,000 │  35,000 │    │
└─────────────────────────────────────────────────┘

장점:

  • 더 직관적인 UX (전체를 보고 판단)
  • 개별 품목 단위 제어 가능 (카테고리 단위보다 유연)
  • SAM 기존 견적 라인 구조와 호환

확장 필요 항목

1. 입력 파라미터 추가

// QuoteBomCalculateRequest 확장
'bracket_inch' => 'nullable|string|in:4,5,6,8',  // 브라켓 인치
'estimated_weight' => 'nullable|numeric',         // 예상 중량
'guide_rail_type' => 'nullable|string',           // 가이드레일 타입
'finishing_type' => 'nullable|string',            // 마감재질
// 체크박스 옵션은 제거 (전체 계산 후 개별 제거 방식)

2. FormulaEvaluatorService 확장

// 5130 계산 함수 추가
private function calculateMotorCapacity(string $productType, float $weight, string $bracketInch): string
{
    // 4.4 모터 용량 계산 공식 구현
}

private function calculateBracketSize(float $weight, ?string $bracketInch = null): string
{
    // 4.7 브라켓 크기 결정 공식 구현
}

private function calculateDynamicItems(array $params): array
{
    // 체크박스 옵션에 따른 동적 항목 생성
}

3. 단가 조회 확장

// 범위 기반 단가 조회 (price_shaft, price_pipe 등)
private function getPriceByRange(string $priceTable, array $conditions): ?float
{
    // JSON 데이터에서 범위 조건으로 단가 조회
}

9.3 API 엔드포인트 설계

기존 엔드포인트 (유지)

POST /api/v1/quotes/calculate-bom
POST /api/v1/quotes/calculate-bom-bulk
GET  /api/v1/quotes/input-schema

신규 엔드포인트 (추가)

POST /api/v1/quotes/calculate-motor
  - 입력: weight, bracket_inch, product_type
  - 출력: motor_capacity, bracket_size

POST /api/v1/quotes/calculate-dynamic-items
  - 입력: model_id, W0, H0, options (체크박스)
  - 출력: dynamic_items[] (모터, 제어기, 부자재)

GET  /api/v1/quotes/price-tables/{table}
  - 테이블: motor, shaft, pipe, angle, raw_materials
  - 출력: 단가표 데이터 (프론트엔드 참조용)

9.4 DB 스키마 변경 (최소화)

변경 불필요:

  • items, prices 테이블: 기존 구조 활용
  • quote_formulas: 기존 수식 시스템 활용

검토 필요:

  • items.metadata JSON 필드에 5130 특수 정보 저장 가능
  • quote_formula_ranges: 범위 기반 단가 조회에 활용

대안:

  • 5130 price_* 테이블 데이터를 quote_formula_ranges로 마이그레이션
  • 또는 별도 kd_price_tables 테이블 생성 (tenant_id=287 전용)

9.5 구현 우선순위

순위 항목 난이도 의존성
1 모터 용량 계산 함수 낮음 없음
2 브라켓 크기 계산 함수 낮음 없음
3 체크박스 옵션 → 동적 항목 중간 1, 2
4 범위 기반 단가 조회 중간 없음
5 API 엔드포인트 추가 낮음 1-4
6 프론트엔드 연동 중간 5

10. Phase 4: 구현 상세 계획

10.1 아키텍처 결정: 하이브리드 접근

배경:

  • 5130 경동 로직은 3차원 조건, 외부 테이블 조회 등 복잡
  • 현재 SAM quote_formulas 시스템으로 표현 불가
  • 범용으로 만들면 다른 테넌트에 불필요한 복잡성

결정:

[범용 레이어] - quote_formulas 테이블
├── 단순 계산, 1차원 범위, 단순 매핑
└── 기본 테넌트들이 사용

[테넌트 전용 레이어] - 전용 Handler 클래스
├── tenant_id = 287 (경동기업)
│   └── KyungdongFormulaHandler.php
└── tenant_id = 기타 → 기본 수식 시스템

10.2 파일 구조

api/app/Services/Quote/
├── QuoteCalculationService.php      # 기존 (수정)
├── FormulaEvaluatorService.php      # 기존 (확장)
└── Handlers/
    └── KyungdongFormulaHandler.php  # 신규 (경동 전용)

api/database/seeders/Kyungdong/
├── KyungdongItemSeeder.php          # 기존 (품목/단가)
└── KyungdongPriceTableSeeder.php    # 신규 (price_* 데이터)

10.3 KyungdongFormulaHandler 설계

namespace App\Services\Quote\Handlers;

class KyungdongFormulaHandler
{
    // 모터 용량 계산 (3차원 조건)
    public function calculateMotorCapacity(
        string $productType,  // screen, steel
        float $weight,
        string $bracketInch   // 4, 5, 6, 8
    ): string;  // 150K, 300K, ...

    // 브라켓 크기 결정
    public function calculateBracketSize(
        float $weight,
        ?string $bracketInch = null
    ): string;  // 530*320, 600*350, 690*390

    // 절곡품 계산 (10종)
    public function calculateSteelItems(array $params): array;

    // 부자재 계산 (3종)
    public function calculatePartItems(array $params): array;

    // 주자재 계산 (스크린)
    public function calculateScreenPrice(
        float $width,
        float $height
    ): float;

    // BDmodels 단가 조회
    private function getBDModelPrice(
        string $modelName,
        string $secondItem,
        ?string $finishingType = null,
        ?string $spec = null
    ): float;

    // price_* 테이블 조회
    private function getPriceFromTable(
        string $tableName,
        array $conditions
    ): float;
}

10.4 FormulaEvaluatorService 확장

// FormulaEvaluatorService.php

public function calculateBomWithDebug(
    string $finishedGoodsCode,
    array $inputs,
    int $tenantId
): array {
    // 테넌트별 분기
    if ($tenantId === 287) {
        return $this->calculateKyungdongBom($finishedGoodsCode, $inputs);
    }

    // 기본 로직 (기존 코드)
    return $this->calculateDefaultBom($finishedGoodsCode, $inputs);
}

private function calculateKyungdongBom(
    string $finishedGoodsCode,
    array $inputs
): array {
    $handler = new KyungdongFormulaHandler();

    // 1. 기본 BOM 전개 (items.bom)
    $staticBom = $this->getStaticBom($finishedGoodsCode);

    // 2. 동적 항목 계산
    $dynamicItems = $handler->calculateDynamicItems($inputs);

    // 3. 전체 항목 병합
    return $this->mergeAndCalculatePrices($staticBom, $dynamicItems, $inputs);
}

10.5 단가 데이터 마이그레이션

옵션 A: 기존 prices 테이블 활용

  • items에 품목 추가, prices에 단가 추가
  • 장점: 기존 구조 활용
  • 단점: 복잡한 조회 조건 표현 어려움

옵션 B: 전용 테이블 생성 (권장)

-- 경동 전용 단가 테이블
CREATE TABLE kd_price_tables (
    id BIGINT PRIMARY KEY,
    tenant_id BIGINT DEFAULT 287,
    table_name VARCHAR(50),      -- motor, shaft, pipe, angle, bdmodels
    item_data JSON,              -- 원본 JSON 데이터
    created_at TIMESTAMP,
    updated_at TIMESTAMP
);

10.6 구현 순서

순서 작업 상태
1 KyungdongFormulaHandler 기본 구조 완료
2 모터/브라켓 계산 메서드 완료
3 kd_price_tables 마이그레이션 완료
4 KdPriceTable 모델 생성 완료
5 KdPriceTableSeeder 생성 완료
6 단가 조회 메서드 (KdPriceTable 연동) 완료
7 부자재 계산 (3종) 완료
8 절곡품 계산 (10종) 대기
9 FormulaEvaluatorService 연동 완료
10 API 테스트 및 검증 대기
8 API 테스트

이 문서는 5130 분석 진행에 따라 지속 업데이트됩니다.