# 비즈니스 로직 분석 > **분석 대상:** 5130 레거시 견적 시스템 비즈니스 로직 > **분석 일자:** 2025-12-19 --- ## 비즈니스 프로세스 개요 ### 견적 생성 플로우 ``` ┌─────────────────────────────────────────────────────────────┐ │ 1. 견적 시작 │ │ └─ 신규 / 복사 / 수주연계 │ ├─────────────────────────────────────────────────────────────┤ │ 2. 기본 정보 입력 │ │ └─ 현장명, 발주처, 담당자 │ ├─────────────────────────────────────────────────────────────┤ │ 3. 제품 선택 │ │ ├─ 대분류: 스크린 / 철재 │ │ ├─ 모델: KSS01, KFS01 등 │ │ └─ 규격: 폭, 높이, 마구리윙 │ ├─────────────────────────────────────────────────────────────┤ │ 4. 옵션 선택 │ │ └─ 절곡, 모터, 보증, 슬랫, 부자재 │ ├─────────────────────────────────────────────────────────────┤ │ 5. 상세 항목 입력 │ │ ├─ 각 행별 위치, 폭, 높이, 수량 입력 │ │ └─ 자동 계산 트리거 │ ├─────────────────────────────────────────────────────────────┤ │ 6. 금액 계산 (자동) │ │ ├─ AJAX → get_screen_amount / get_slat_amount │ │ ├─ 18개 항목별 단가 조회 및 계산 │ │ └─ 합계 산출 │ ├─────────────────────────────────────────────────────────────┤ │ 7. 할인 적용 │ │ └─ 할인율 / 할인액 입력 → 최종금액 계산 │ ├─────────────────────────────────────────────────────────────┤ │ 8. 저장 │ │ ├─ 견적번호 생성 (KD-PR-YYMMDD-NN) │ │ └─ DB 저장 (estimate 테이블) │ └─────────────────────────────────────────────────────────────┘ ``` --- ## 1. 견적 유형별 처리 ### 스크린 견적 (Screen) | 특성 | 값 | |------|-----| | 대분류 | 스크린 | | 주자재 소재 | 실리카, 와이어 | | 면적 계산 | (높이 + 550) × 폭 / 1,000,000 m² | | 기본 제작폭 | 160mm | | 전용 항목 | L바, 보강평철, 무게평철, 환봉 | ### 슬랫 견적 (Slat/철재) | 특성 | 값 | |------|-----| | 대분류 | 철재 | | 주자재 소재 | 방화슬랫 | | 면적 계산 | (높이 + 50) × 폭 / 1,000,000 m² | | 기본 제작폭 | 110mm | | 전용 항목 | 조인트바 | --- ## 2. 옵션 체크박스 로직 ### 옵션별 영향 항목 | 옵션 | 변수 | 영향받는 항목 | |------|------|---------------| | 절곡 | `steel` | 케이스, 케이스용 연기차단재, 마구리, 가이드레일, 레일용 연기차단재, 하장바, L바(스크린), 보강평철(스크린) | | 모터 | `motor` | 모터 가격 포함/미포함 | | 보증 | `warranty` | 보증기간 표시 (인정) | | 슬랫 | `slatcheck` | 주자재(슬랫), 조인트바 | | 부자재 | `partscheck` | 감기샤프트, 각파이프, 앵글 | ### 조건부 계산 로직 ```php // 절곡 옵션 체크 시 if ($steel == '1') { // 케이스, 연기차단재, 레일, 하장바 등 계산 $caseAmount = calculateCase($width, $caseType, $itemList); $smokebanAmount = calculateSmokeban($width, $itemList); // ... } else { // 해당 항목 0원 처리 $caseAmount = 0; $smokebanAmount = 0; } // 모터 옵션 체크 시 if ($motor == '1') { $motorAmount = getMotorPrice($motorCapacity); } else { $motorAmount = 0; } ``` --- ## 3. 단가 조회 로직 ### 조회 우선순위 1. **BDmodels 테이블**: 모델별 품목 단가 2. **price_* 테이블**: 품목별 세부 단가 3. **기본값**: 조회 실패 시 기본 단가 적용 ### 단가 조회 함수 ```php // fetch_unitprice.php // 1. 모터 단가 조회 function getMotorPrice($capacity) { // price_motor 테이블에서 용량별 단가 조회 $sql = "SELECT unit_price FROM price_motor WHERE capacity = ?"; // ... } // 2. 샤프트 단가 조회 function getShaftPrice($length) { // price_shaft 테이블에서 길이별 단가 조회 $sql = "SELECT unit_price FROM price_shaft WHERE ? BETWEEN min_length AND max_length"; // ... } // 3. BDmodels에서 품목 단가 조회 function getBDModelPrice($modelname, $itemname, $size) { $sql = "SELECT itemList FROM BDmodels WHERE modelname = ? AND itemname = ?"; // JSON 파싱 후 사이즈에 맞는 가격 반환 } ``` --- ## 4. 금액 계산 플로우 ### 스크린 금액 계산 (get_screen_amount.php) ``` ┌─────────────────────────────────────────────────────────────┐ │ 입력값 │ │ - 폭(col2), 높이(col3), 수량(col4), 소재(col5) │ │ - 케이스타입(col6), 레일타입(col7), 설치방식(col8) │ │ - 체크박스옵션 (steel, motor, warranty, slatcheck, partscheck)│ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 1. 면적 계산 │ │ area = (height + 550) × width / 1,000,000 │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 2. 중량 계산 (모터 용량 결정용) │ │ weight = area × 소재별_단위중량 │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 3. 모터 용량 결정 │ │ motorCapacity = searchBracketSize(weight, inch) │ │ → 150K / 300K / 500K / 800K / 1000K │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 4. 18개 항목별 금액 계산 │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 1. 검사비: inspectionFee (고정) │ │ │ │ 2. 주자재: area × 소재단가 │ │ │ │ 3. 모터: getMotorPrice(capacity) × motor체크 │ │ │ │ 4. 제어기: getControllerPrice(type) × motor체크 │ │ │ │ 5. 케이스: (width+제작폭) × m당단가 × steel체크 │ │ │ │ 6. 케이스연기차단재: width × m당단가 × steel체크 │ │ │ │ 7. 마구리: 2개 × 개당단가 × steel체크 │ │ │ │ 8. 앵글: 규격별단가 × steel체크 │ │ │ │ 9. 가이드레일: (height+레일여유) × m당단가 × steel │ │ │ │ 10. 레일연기차단재: height × m당단가 × steel │ │ │ │ 11. 하장바: width × m당단가 × steel체크 │ │ │ │ 12. L바: width × m당단가 × steel체크 │ │ │ │ 13. 보강평철: width × m당단가 × steel체크 │ │ │ │ 14. 샤프트: getShaftPrice(width) × partscheck │ │ │ │ 15. 무게평철: weight계산 × 단가 │ │ │ │ 16. 환봉: 길이계산 × m당단가 │ │ │ │ 17. 각파이프: 길이계산 × m당단가 × partscheck │ │ │ │ 18. 앵글: 길이계산 × m당단가 × partscheck │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 5. 행 합계 │ │ rowTotal = Σ(항목별금액) × 수량 │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 6. 전체 합계 │ │ estimateTotal = Σ(모든행 rowTotal) │ │ EstimateFinalSum = estimateTotal - 할인액 │ └─────────────────────────────────────────────────────────────┘ ``` --- ## 5. 모터 용량 결정 로직 ### 중량 + 인치 기반 판단 ```php function searchBracketSize($motorWeight, $bracketInch = null) { $weight = floatval($motorWeight); $inch = intval($bracketInch); // 인치별 중량 기준 if ($inch > 0) { // 4인치 기준 if ($inch == 4 && $weight <= 300) → 300K if ($inch == 4 && $weight <= 400) → 400K // 5인치 기준 if ($inch == 5 && $weight <= 246) → 300K if ($inch == 5 && $weight <= 327) → 400K if ($inch == 5 && $weight <= 500) → 500K if ($inch == 5 && $weight <= 600) → 600K // 6인치 기준 if ($inch == 6 && $weight <= 208) → 300K if ($inch == 6 && $weight <= 277) → 400K if ($inch == 6 && $weight <= 424) → 500K if ($inch == 6 && $weight <= 508) → 600K if ($inch == 6 && $weight <= 800) → 800K if ($inch == 6 && $weight <= 1000) → 1000K // 8인치 기준 if ($inch == 8 && $weight <= 324) → 500K if ($inch == 8 && $weight <= 388) → 600K if ($inch == 8 && $weight <= 611) → 800K if ($inch == 8 && $weight <= 1000) → 1000K } else { // 인치 없이 중량만으로 판단 if ($weight <= 300) → 300K if ($weight <= 400) → 400K if ($weight <= 500) → 500K if ($weight <= 600) → 600K if ($weight <= 800) → 800K if ($weight <= 1000) → 1000K } } ``` ### 브라켓 사이즈 매핑 | 모터 용량 | 브라켓 사이즈 | |-----------|---------------| | 300K, 400K | 530×320 | | 500K, 600K | 600×350 | | 800K, 1000K | 690×390 | --- ## 6. 견적번호 생성 ### 형식 ``` KD-PR-YYMMDD-NN KD: 경동 (회사코드) PR: 프로젝트 YYMMDD: 날짜 (년월일 6자리) NN: 일련번호 (01~99, 당일 기준) ``` ### 생성 로직 ```php // generate_serial_pjnum.php function generatePjnum($pdo) { $today = date('ymd'); $prefix = "KD-PR-{$today}-"; // 오늘 날짜의 마지막 번호 조회 $sql = "SELECT pjnum FROM estimate WHERE pjnum LIKE ? ORDER BY pjnum DESC LIMIT 1"; $stmh = $pdo->prepare($sql); $stmh->execute([$prefix . '%']); $row = $stmh->fetch(); if ($row) { // 마지막 번호 추출 후 +1 $lastNum = intval(substr($row['pjnum'], -2)); $nextNum = str_pad($lastNum + 1, 2, '0', STR_PAD_LEFT); } else { $nextNum = '01'; } return $prefix . $nextNum; } ``` --- ## 7. 금액 처리 규칙 ### 단위 변환 | 항목 | 입력 단위 | 계산 단위 | 비고 | |------|----------|----------|------| | 폭/높이 | mm | m | /1000 변환 | | 면적 | - | m² | 폭×높이/1,000,000 | | 중량 | - | kg | 면적×단위중량 | | 금액 | - | 원 | 천원 단위 반올림 | ### 반올림 규칙 ```javascript // calculation.js // 금액은 천원 단위에서 반올림 roundedAreaPrice = Math.round(areaPrice / 1000) * 1000; // 면적은 소수점 2자리 area = Math.round(area * 100) / 100; ``` ### 수동 편집 처리 ```javascript // 수동 편집된 셀은 배경색 변경 $('.manually-edited').css('background-color', '#f8d7da'); // 자동 계산 시 수동 편집 값 유지 옵션 if (!isManuallyEdited) { cell.val(calculatedValue); } ``` --- ## 8. 데이터 저장 규칙 ### 저장 전 검증 1. 필수값 확인: 현장명, 발주처, 담당자 2. 수치 변환: 콤마 제거, 정수 변환 3. 권한 확인: 레벨 5 이하 ### 저장 데이터 ```php // insert.php $data = [ 'pjnum' => generatePjnum(), 'indate' => date('Y-m-d'), 'orderman' => $_SESSION['name'], 'outworkplace' => $outworkplace, 'major_category' => $major_category, 'model_name' => $model_name, 'makeWidth' => intval(str_replace(',', '', $makeWidth)), 'makeHeight' => intval(str_replace(',', '', $makeHeight)), 'maguriWing' => $maguriWing, 'inspectionFee' => intval(str_replace(',', '', $inspectionFee)), 'estimateList' => json_encode($estimateList), 'estimateList_auto' => json_encode($estimateList_auto), 'estimateSlatList' => json_encode($estimateSlatList), 'estimateSlatList_auto' => json_encode($estimateSlatList_auto), 'estimateTotal' => intval(str_replace(',', '', $estimateTotal)), 'steel' => $steel, 'motor' => $motor, 'warranty' => $warranty, 'slatcheck' => $slatcheck, 'partscheck' => $partscheck ]; ``` --- ## 9. SAM 이관 시 로직 변경 ### Service 클래스 분리 ```php // app/Services/QuotationService.php class QuotationService { // 1. 견적 생성 public function createQuote(array $data): Quote { } // 2. 금액 계산 public function calculateAmount(Quote $quote): array { } // 3. 스크린 계산 protected function calculateScreenAmount(array $details): array { } // 4. 슬랫 계산 protected function calculateSlatAmount(array $details): array { } // 5. 모터 용량 결정 protected function determineMotorCapacity(float $weight, ?int $inch): int { } // 6. 단가 조회 protected function getUnitPrice(string $itemCode, array $params): float { } } ``` ### 계산 로직 캡슐화 ```php // app/ValueObjects/QuoteDimension.php class QuoteDimension { public function __construct( public readonly int $width, public readonly int $height, public readonly int $wing = 50 ) {} public function getAreaForScreen(): float { return ($this->height + 550) * $this->width / 1000000; } public function getAreaForSlat(): float { return ($this->height + 50) * $this->width / 1000000; } } ``` ### 옵션 처리 ```php // app/ValueObjects/QuoteOptions.php class QuoteOptions { public function __construct( public readonly bool $steel = false, public readonly bool $motor = false, public readonly bool $warranty = false, public readonly bool $slat = false, public readonly bool $parts = false ) {} public static function fromArray(array $data): self { return new self( steel: $data['steel'] ?? false, motor: $data['motor'] ?? false, warranty: $data['warranty'] ?? false, slat: $data['slat'] ?? false, parts: $data['parts'] ?? false ); } public function toJson(): string { return json_encode([ 'steel' => $this->steel, 'motor' => $this->motor, 'warranty' => $this->warranty, 'slat' => $this->slat, 'parts' => $this->parts ]); } } ``` --- ## 참조 파일 - `5130/estimate/get_screen_amount.php` - 스크린 계산 로직 - `5130/estimate/get_slat_amount.php` - 슬랫 계산 로직 - `5130/estimate/fetch_unitprice.php` - 단가 조회 함수 - `5130/estimate/insert.php` - 저장 로직 - `5130/estimate/generate_serial_pjnum.php` - 번호 생성