견적관리 그리드 V1 컬럼 보강(제품구분·국내해외·반납사유) + wace 1:1 검증 문서·자동 검증 SQL

- getList SQL: 라인 집계에 product_summary(=PRODUCT_NAME, contract_item.product distinct join) / return_reason_summary 추가. wace는 헤더 product 폐지·라인으로 이동(운영 90건 contract_mgmt.product NULL) → 라인 집계로 그리드 표시
- GRID_COLUMNS 3개 추가: 제품구분 / 국내해외 / 반납사유
- searchForm.search_partName 필드 추가(초기화 포함). 검색 폼 UI는 PartSelect mode=partName 이미 존재
- docs/migration/sales/01-estimate-verify.md: wace ↔ RPS 항목 매핑 / 운영 데이터 코드 체계 / 갭 우선순위
- scripts/verify-estimate.sql: BEGIN/ROLLBACK 5개 시나리오 (등록·수정·G1·수주취소·그리드) 자동 검증

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
hjjeong
2026-05-11 09:29:43 +09:00
parent 7cbf938345
commit 12ea68616d
5 changed files with 456 additions and 5 deletions
+197
View File
@@ -0,0 +1,197 @@
# 01. 견적관리 wace 1:1 검증
> 작성: 2026-05-09 / 사이클: 구조적 검증 1차 (견적관리 메뉴)
> 목적: wace 운영 화면과 RPS 견적관리를 항목/식별자/채번 단위로 1:1 매칭 + 갭 도출 + 자동 검증 시나리오
## 1. 항목 매핑
### 1.1 등록/수정 폼 — 헤더 8개 (`estimateRegistFormPopup.jsp` 1행/2행)
| # | wace 라벨 | wace name | RPS 폼 | 코드 그룹 | 필수 | 운영 데이터 |
|---|---|---|---|---|---|---|
| 1 | 주문유형 | `category_cd` | `EstimateBody.category_cd` | `0000167` | ✅ | 0001792(판매)·0001791(수리)·0900221(자체개발) |
| 2 | 국내/해외 | `area_cd` | `EstimateBody.area_cd` | `0001219` | ✅ | 0001220(국내)·0001221(해외) |
| 3 | 고객사 | `customer_objid` | `EstimateBody.customer_objid` | — | ✅ | `C_{customer_code}` 90건 일치 |
| 4 | 유/무상 | `paid_type` | `EstimateBody.paid_type` | (raw `paid`/`free`) | ✅ | paid 85·free 4·NULL 1 |
| 5 | 접수일 | `receipt_date` | `EstimateBody.receipt_date` | (date varchar) | ✅ | YYYY-MM-DD |
| 6 | 견적환종 | `contract_currency` | `EstimateBody.contract_currency` | `0001533` | — | 0001566(원)·0001534(달러)·0001537(엔) |
| 7 | 견적환율 | `exchange_rate` | `EstimateBody.exchange_rate` | (raw text) | — | |
| 8 | 결재여부 | `approval_required` | `EstimateBody.approval_required` | (Y/N 체크박스) | ✅ | 90건 N (결재모듈 미도입) |
### 1.2 등록/수정 폼 — 라인 8개 + 삭제
| # | wace 라벨 | wace name | RPS 라인 | 컬럼/그룹 | 필수 |
|---|---|---|---|---|---|
| 1 | No (자동) | (auto seq) | `seq` | integer | — |
| 2 | 제품구분 | `item_product[]` | `EstimateItem.product` | comm_code `0000001` | ✅ |
| 3 | 품번 | `item_part_no_select[]` | `EstimateItem.part_objid`+`part_no` | PartSelect | ✅ |
| 4 | 품명 | `item_part_name_select[]` | `EstimateItem.part_name` | PartSelect | ✅ |
| 5 | S/N | `item_serial_no[]` | `EstimateItem.serials[]` | S/N 다이얼로그 | — |
| 6 | 견적수량 | `item_quantity[]` | `EstimateItem.quantity` | integer | — |
| 7 | 요청납기 | `item_due_date[]` | `EstimateItem.due_date` | date varchar | — |
| 8 | 반납사유 | `item_return_reason[]` | `EstimateItem.return_reason` | comm_code `0001810` | — |
| 9 | 고객요청사항 | `item_customer_request[]` | `EstimateItem.customer_request` | text | — |
### 1.3 그리드 컬럼 — wace 활성 22개
| # | wace title | wace field | RPS GRID_COLUMNS | 상태 |
|---|---|---|---|---|
| 1 | 영업번호 | `CONTRACT_NO` | `contract_no` (frozen) | ✅ |
| 2 | 주문유형 | `CATEGORY_NAME` | `category_name` | ✅ |
| 3 | 접수일 | `RECEIPT_DATE` | `receipt_date` | ✅ |
| 4 | 요청납기 | `EARLIEST_DUE_DATE` | `earliest_due_date_label` | ✅ |
| 5 | 고객사 | `CUSTOMER_NAME` | `customer_name` | ✅ |
| 6 | 품명 | `ITEM_SUMMARY` | `item_summary` | ✅ |
| 7 | 견적수량 | `ESTIMATE_QUANTITY` | `estimate_quantity` | ✅ |
| 8 | 유/무상 | `PAID_TYPE` | `paid_type_name` | ✅ |
| 9 | 공급가액 | `EST_TOTAL_AMOUNT` | `est_total_amount` | ✅ |
| 10 | 원화환산공급가액 | `EST_TOTAL_AMOUNT_KRW` | `est_total_amount_krw` | ✅ |
| 11 | 견적현황 | `EST_STATUS` | `est_status` (folder) | ✅ |
| 12 | 추가견적 | `ADD_EST_CNT` | `add_est_cnt` (clip) | ✅ |
| 13 | 결재상태 | `APPR_STATUS` | `appr_status` | ✅ |
| 14 | 메일발송 | `MAIL_SEND_STATUS` | `mail_send_status_label` | ✅ |
| 15 | 환종 | `CONTRACT_CURRENCY_NAME` | `contract_currency_name` | ✅ |
| 16 | 환율 | `EXCHANGE_RATE` | `exchange_rate` | ✅ |
| 17 | S/N | `SERIAL_NO` | `serial_no` | ✅ |
| 18 | 품번 | `PART_NO` | `part_no` | ✅ |
| 19 | 작성자 | `WRITER_NAME` | `writer_name` | ✅ |
| 20 | **제품구분** | `PRODUCT_NAME` | (없음) | 🔴 갭 |
| 21 | **국내/해외** | `AREA_NAME` | (없음) | 🔴 갭 |
| 22 | **반납사유** | `RETURN_REASON_SUMMARY` | (없음) | 🔴 갭 (집계 컬럼 신설 필요) |
### 1.4 검색 폼 — wace 활성 7개
| # | wace 라벨 | wace name | RPS searchForm | 상태 |
|---|---|---|---|---|
| 1 | 주문유형 | `category_cd` | `category_cd` | ✅ |
| 2 | 고객사 | `customer_objid` | `customer_objid` | ✅ |
| 3 | 품번 | `search_partNo` | `search_partObjId` | 🟡 (PartSelect로 part_objid 단일 검색) |
| 4 | **품명** | `search_partName` | (없음) | 🔴 갭 |
| 5 | S/N | `search_serialNo` | `search_serialNo` | ✅ |
| 6 | 결재상태 | `appr_status` | `appr_status` | ✅ |
| 7 | 접수일 | `receipt_start_date~end_date` | `receipt_start_date/end_date` | ✅ |
### 1.5 액션 버튼
| 버튼 | wace endpoint | RPS 동작 | 상태 |
|---|---|---|---|
| 조회 | `/contractMgmt/estimateGridList.do` | `GET /sales/estimate/list` | ✅ |
| 삭제 | `/contractMgmt/deleteEstimateMgmtInfo.do` | `DELETE /sales/estimate/:id` | ✅ |
| 견적요청등록/수정 | `/contractMgmt/saveContractMgmtInfo.do` | `POST/PUT /sales/estimate` | ✅ (선택 시 수정 분기) |
| 견적작성 | `/contractMgmt/saveEstimate.do | saveEstimate2.do` (template1/2) | placeholder | 🟠 G5 별도 PR |
| 결재상신 | `/contractMgmt/checkApprovalRequired.do` → 아마란스 SSO | placeholder | 🟠 G4 별도 PR |
| 메일발송 | `/contractMgmt/sendEstimateMail.do` | `POST /sales/estimate/mail` (mail_log INSERT만) | 🟡 SMTP 미구현(G6) |
---
## 2. 운영 데이터 코드 체계 (90건 검증 완료)
### 2.1 식별자
| 항목 | 형식 | 검증 |
|---|---|---|
| `contract_mgmt.objid` | varchar (raw integer hash 또는 'CM-...') | wace 운영은 raw integer (문자열로 보관) |
| `contract_mgmt.contract_no` | `{YY}C-{NNNN}` | **90/90건 일치** (regex `^[0-9]{2}C-[0-9]{4}$`) |
| `contract_mgmt.customer_objid` | `C_{customer_code}` (10자리 padded) | **90/90건 customer_mng.customer_code로 매칭** |
| `contract_item.objid` | varchar (raw integer 또는 'CI-...') | |
### 2.2 comm_code 그룹 ID
| 용도 | parent_code_id | 자식 예시 |
|---|---|---|
| 주문유형 (category_cd) | `0000167` | 0001791(수리)/0001792(판매)/0900221(자체개발)/0000170(오버홀)/0000171(개조)/0000168(신규개발)/0001790(견적)/0900214(계획생산) |
| 국내/해외 (area_cd) | `0001219` | 0001220(국내)/0001221(해외) |
| 제품구분 (product) | `0000001` | 0000928(Machine)/0000930(A/S)/0001525(D/S)/0001539(B/S)/0001793(C/T)/0001794(A/C)/0001807(W/M)/0001809(기타) |
| 환종 (contract_currency) | `0001533` | 0001534(달러$)/0001535(유로€)/0001536(위안¥)/0001537(엔¥)/0001566(원₩) |
| 수주상태 (contract_result) | `0000963` | 0000964(수주)/0000965(Cancel)/0000966(Hold)/0000968(수주FCST) |
| 반납사유 (return_reason) | `0001810` | 0001811(수리불가) |
### 2.3 채번 룰
| 항목 | 룰 | 적용 위치 |
|---|---|---|
| `contract_no` | `{YY}C-{NNNN}` (4자리 zero-pad, 같은 prefix MAX+1) | `salesOrderMgmtService.generateContractNo` |
| `project_no` | `{주문유형1}-{제품구분2}-{YYMMDD}-{NNN}` (예: R-CT-260507-001) | `salesOrderMgmtService.generateProjectNo` |
---
## 3. 발견된 갭 (우선순위)
| # | 우선 | 항목 | 권장 작업 |
|---|---|---|---|
| V1 | 🔴 | 그리드 컬럼 3개 누락 (제품구분/국내해외/반납사유) | RPS GRID_COLUMNS 추가 + getList SQL에 PRODUCT_NAME/AREA_NAME/RETURN_REASON_SUMMARY 컬럼 추가. RETURN_REASON_SUMMARY는 contract_item 집계로 LATERAL JOIN |
| V2 | 🟠 | 품명 검색 누락 (`search_partName`) | searchForm에 `search_partName` 추가 + 백엔드 SQL where 조건 추가 |
| V3 | 🟡 | paid_type NULL 1건 (운영 데이터 이슈) | 폼에서 신규 시 `paid` default 강제 — 이미 적용. 기존 NULL 데이터 정리는 별도 |
| V4 | 🟢 | 결재모듈 (G4) — 모든 운영 데이터 approval_required='N' | G4 별도 PR |
| V5 | 🟢 | 견적작성 PDF (G5) | G5 별도 PR |
| V6 | 🟢 | SMTP 실발송 (G6) | G6 별도 PR |
---
## 4. 자동 검증 시나리오 (BEGIN/ROLLBACK)
각 시나리오는 dev DB에서 트랜잭션 안에서 실제 SQL 실행 + 결과 검증 후 ROLLBACK. 영향 0.
### 시나리오 1: 신규 견적요청 등록
```
BEFORE: contract_mgmt 90 / contract_item N0 / contract_item_serial S0
- INSERT contract_mgmt 1건 (contract_no=26C-0802, customer_objid='C_0000005546', ...)
- INSERT contract_item 1건 (product=0001793, part_objid=1868255719, quantity=2)
- INSERT contract_item_serial 0건 (S/N 미입력)
AFTER: contract_mgmt 91 / contract_item N0+1 / serial S0
ROLLBACK → 모두 원복
```
### 시나리오 2: 견적요청 수정 (라인 1→2 확장)
```
BEFORE: 26C-0801 contract_item 1건
- upsertItems: 기존 라인 status='INACTIVE'
- INSERT 새 라인 2건 (objid 새로 발급, ON CONFLICT 미발동)
AFTER: contract_item ACTIVE 2건, INACTIVE 1건 (= 누적 3건)
ROLLBACK
```
### 시나리오 3: 견적요청 삭제
```
BEFORE: 26C-XXXX contract_item N건, contract_item_serial M건
- UPDATE contract_item_serial SET status='INACTIVE' WHERE item_objid IN (...)
- DELETE contract_item WHERE contract_objid=$
- DELETE contract_mgmt WHERE objid=$
AFTER: 모두 사라짐
ROLLBACK
```
### 시나리오 4: 수주확정 → 프로젝트 자동생성 (G1)
```
BEFORE: project_mgmt 89 / contract_mgmt.contract_result NULL
- UPDATE contract_mgmt SET contract_result='0000964'
- 트리거: createProjectsFromContract
- hasProject=false
- contract_item N개 루프 → Machine 분기 → project_no 채번 → INSERT
AFTER: project_mgmt 89+N 또는 89+sum(Machine_qty)+non_machine_count
ROLLBACK
```
(검증 완료: 26C-0801 1라인 C/T qty=2 → project_no=R-CT-260508-001 1건 INSERT)
### 시나리오 5: 수주취소 (cancel_qty 입력)
```
BEFORE: contract_item.cancel_qty NULL
- UPDATE contract_item SET cancel_qty='1', chgdate=NOW(), chg_user_id=$
- contract_mgmt.contract_result 미변경
검증: cancel_qty < order_qty (전체 취소 불가)
ROLLBACK
```
---
## 5. 다음 단계
1. **갭 V1·V2 수정** (그리드 3컬럼 + 품명 검색) → 사용자 확인 후 커밋
2. **자동 검증 SQL 스크립트** 정리 (`scripts/verify-estimate.sql` — BEGIN/ROLLBACK 트랜잭션 모음)
3. **사용자 dev 환경 최종 확인** → 견적관리 메뉴 종결 → 주문관리 진행