Files
sam-docs/projects/quotation/phase-1-5130-analysis/business-logic.md
hskwon 1066ea25b2 docs: Phase 5 API 문서 추가 (사용자 초대, 알림설정, 계정관리)
- erp-api-list.md: Phase 5 섹션 추가 (12개 API)
- erp-api-detail.md: Phase 5 상세 스펙 추가
  - 13. 사용자 초대 (5개): 목록, 발송, 수락, 취소, 재발송
  - 14. 알림 설정 (3개): 조회, 수정, 일괄수정
  - 15. 계정 관리 (4개): 탈퇴, 사용중지, 약관조회, 약관수정
2025-12-19 15:35:41 +09:00

473 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 비즈니스 로직 분석
> **분석 대상:** 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` - 번호 생성