- erp-api-list.md: Phase 5 섹션 추가 (12개 API) - erp-api-detail.md: Phase 5 상세 스펙 추가 - 13. 사용자 초대 (5개): 목록, 발송, 수락, 취소, 재발송 - 14. 알림 설정 (3개): 조회, 수정, 일괄수정 - 15. 계정 관리 (4개): 탈퇴, 사용중지, 약관조회, 약관수정
473 lines
19 KiB
Markdown
473 lines
19 KiB
Markdown
# 비즈니스 로직 분석
|
||
|
||
> **분석 대상:** 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` - 번호 생성
|