판매관리 그리드 V1 컬럼 8개 보강 + cu01_cnt 실데이터 + wace 1:1 검증 문서·자동 검증 SQL

- getList SQL: attach_file_info LATERAL JOIN으로 cu01_cnt(주문서첨부) 실데이터 (contract_mgmt.objid 기반, doc_type IN FTC_ORDER/ORDER)
- SaleListRow 타입: product_type_name/nation_name/receipt_date/customer_request/manager_name/payment_type_name/cu01_cnt 보강
- GRID_COLUMNS 8개 추가: 제품구분/국내해외/접수일/고객사요청사항/주문서첨부/출하방법/담당자/인도조건 (wace 36/36 일치)
- docs/migration/sales/03-sale-verify.md: wace ↔ RPS 매핑 / 갭 처리
- scripts/verify-sale.sql: BEGIN/ROLLBACK 2개 시나리오 (그리드 V1 / 판매상태 wace 로직)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
hjjeong
2026-05-11 09:38:57 +09:00
parent 13b4efd2fe
commit a0f6e0fa26
5 changed files with 184 additions and 2 deletions
+11 -2
View File
@@ -169,11 +169,12 @@ export async function getSaleList(filter: SaleListFilter) {
,CM.customer_request ,CM.customer_request
,T.sales_deadline_date ,T.sales_deadline_date
,T.regdate ,T.regdate
/* 출하지시상태/생산상태/분할S/N/거래명세서/주문서첨부 — 1차 placeholder */ /* 출하지시상태/생산상태/분할S/N/거래명세서 — 1차 placeholder */
,NULL::text AS production_status ,NULL::text AS production_status
,NULL::text AS split_serial_no ,NULL::text AS split_serial_no
,'N'::text AS has_transaction_statement ,'N'::text AS has_transaction_statement
,0 AS cu01_cnt /* 주문서첨부 카운트 — contract_mgmt.objid 기반 (wace 동일) */
,COALESCE(AF.cu01_cnt, 0) AS cu01_cnt
FROM project_mgmt T FROM project_mgmt T
LEFT JOIN contract_mgmt CM ON CM.objid = T.contract_objid LEFT JOIN contract_mgmt CM ON CM.objid = T.contract_objid
LEFT JOIN contract_item CI ON CI.objid = T.contract_item_objid AND CI.status = 'ACTIVE' LEFT JOIN contract_item CI ON CI.objid = T.contract_item_objid AND CI.status = 'ACTIVE'
@@ -202,6 +203,14 @@ export async function getSaleList(filter: SaleListFilter) {
AND CIS.serial_no IS NOT NULL AND CIS.serial_no != '' AND CIS.serial_no IS NOT NULL AND CIS.serial_no != ''
GROUP BY CIS.item_objid GROUP BY CIS.item_objid
) CIS_AGG ON CIS_AGG.item_objid = T.contract_item_objid ) CIS_AGG ON CIS_AGG.item_objid = T.contract_item_objid
/* 주문서첨부 카운트 (contract_mgmt.objid 기반, doc_type FTC_ORDER/ORDER — wace 동일) */
LEFT JOIN (
SELECT target_objid,
COUNT(*) FILTER (WHERE doc_type IN ('FTC_ORDER','ORDER')) AS cu01_cnt
FROM attach_file_info
WHERE UPPER(status) = 'ACTIVE'
GROUP BY target_objid
) AF ON AF.target_objid = T.contract_objid
${where} ${where}
ORDER BY T.regdate DESC NULLS LAST, T.project_no DESC ORDER BY T.regdate DESC NULLS LAST, T.project_no DESC
`; `;
+82
View File
@@ -0,0 +1,82 @@
# 03. 판매관리 wace 1:1 검증
> 작성: 2026-05-11 / 사이클: 구조적 검증 3차 (판매관리 메뉴)
> 원본: `wace_plm/WebContent/WEB-INF/view/salesmgmt/salesMgmt/salesMgmtList.jsp`
> 대상: `app/(main)/COMPANY_16/sales/sale/page.tsx`
> 메인 테이블: `project_mgmt T LEFT JOIN contract_mgmt CM LEFT JOIN sales_registration SR`
## 1. 그리드 컬럼 — wace 활성 36개 vs RPS 36개 (보강 후 일치)
| # | wace title | RPS GRID_COLUMNS key | 상태 |
|---|---|---|---|
| 1 | 프로젝트번호 (frozen) | `project_no` | ✅ |
| 2 | 주문유형 | `order_type_name` | ✅ |
| 3 | 발주일 | `order_date` | ✅ |
| 4 | 발주번호 | `po_no` | ✅ |
| 5 | 요청납기 | `request_date` | ✅ |
| 6 | 출하일 | `shipping_date` | ✅ |
| 7 | 고객사 | `customer` | ✅ |
| 8 | 품명 | `product_name` | ✅ |
| 9 | 수주수량 | `order_quantity` | ✅ |
| 10 | 판매수량 | `sales_quantity` | ✅ |
| 11 | 잔량 | `remaining_quantity` | ✅ |
| 12 | 판매단가 | `sales_unit_price` | ✅ |
| 13 | 판매공급가액 | `sales_supply_price` | ✅ |
| 14 | 부가세 | `sales_vat` | ✅ |
| 15 | 판매총액 | `sales_total_amount` | ✅ |
| 16 | 판매원화총액 | `sales_total_amount_krw` | ✅ |
| 17 | 잔량원화총액 | `remaining_amount_krw` | ✅ |
| 18 | 수주상태 | `order_status_name` | ✅ |
| 19 | 판매상태 | `sales_status` | ✅ (wace 로직: 미판매/완판/분할판매 동적) |
| 20 | 생산상태 | `production_status` | ✅ (placeholder) |
| 21 | 출하지시상태 | `shipping_order_status` | ✅ |
| 22 | 유/무상 | `payment_type_name` | ✅ |
| 23 | 환종 | `sales_currency_name` | ✅ |
| 24 | 환율 | `sales_exchange_rate` | ✅ |
| 25 | S/N | `serial_no` | ✅ |
| 26 | 분할S/N | `split_serial_no` | ✅ (placeholder) |
| 27 | 품번 | `product_no` | ✅ |
| 28 | **제품구분** | `product_type_name` | ✅ (신규) |
| 29 | **국내/해외** | `nation_name` | ✅ (신규) |
| 30 | **접수일** | `receipt_date` | ✅ (신규) |
| 31 | **고객사요청사항** | `customer_request` | ✅ (신규) |
| 32 | **주문서첨부** | `cu01_cnt` (clip) | ✅ (신규, attach_file_info LATERAL JOIN) |
| 33 | **출하방법** | `shipping_method` | ✅ (신규) |
| 34 | **담당자** | `manager_name` | ✅ (신규) |
| 35 | **인도조건** | `incoterms` | ✅ (신규) |
| 36 | 거래명세서 | `has_transaction_statement` | ✅ (placeholder) |
## 2. SQL 핵심 구조
```
FROM project_mgmt T
LEFT JOIN contract_mgmt CM ON CM.objid = T.contract_objid
LEFT JOIN contract_item CI ON CI.objid = T.contract_item_objid
LEFT JOIN customer_mng C ON C.customer_code = SUBSTRING(T.customer_objid, 3)
LEFT JOIN sales_registration SR ON SR.project_no = T.project_no
LEFT JOIN user_info U_MGR ON U_MGR.user_id = SR.manager_user_id
LEFT JOIN comm_code CC_* ON CC_*.code_id = T.* AND status='active' (CAT/AREA/PRD/RES/CUR/CUR_S)
LEFT JOIN SR_AGG ON SR_AGG.project_no LIKE T.project_no || '%' (판매수량 합계 — 분할판매 패턴)
LEFT JOIN CIS_AGG ON CIS_AGG.item_objid = T.contract_item_objid (S/N 집계)
LEFT JOIN AF ON AF.target_objid = T.contract_objid (주문서첨부 신규)
```
## 3. 갭 처리
| # | 갭 | 처리 |
|---|---|---|
| **S1** | 그리드 8개 누락 (제품구분/국내해외/접수일/고객사요청사항/주문서첨부/출하방법/담당자/인도조건) | ✅ 본 커밋에서 추가 |
| **S2** | `cu01_cnt` placeholder 0 → 실제 attach_file_info 카운트 | ✅ LATERAL JOIN 추가 |
| S3 | 분할S/N (split_serial_no) 실데이터 | 🟡 백로그 — sales_registration 분할 LIKE 패턴 활용 시 추후 |
| S4 | 거래명세서/생산상태 실데이터 | 🟡 백로그 |
## 4. 자동 검증 결과 (`scripts/verify-sale.sql`)
- 그리드 8개 신규 컬럼 SELECT 정상 (10 rows 샘플 모두 product_type_name/nation_name 표시)
- `cu01_cnt` 운영 데이터에서 모두 0 (현재 attach_file_info 추가견적/주문서 doc_type 없음)
- 판매상태 wace 로직 (미판매/완판/분할판매) 정합
## 5. 결론
판매관리 메뉴 wace 운영 화면과 1:1 정합 (36/36 컬럼).
**다음 메뉴**: 매출관리 (revenue) — `shipment_log` 기반.
@@ -48,6 +48,14 @@ const GRID_COLUMNS: DataGridColumn[] = [
{ key: "serial_no", label: "S/N", width: "w-[140px]" }, { key: "serial_no", label: "S/N", width: "w-[140px]" },
{ key: "split_serial_no", label: "분할S/N", width: "w-[140px]" }, { key: "split_serial_no", label: "분할S/N", width: "w-[140px]" },
{ key: "product_no", label: "품번", width: "w-[120px]" }, { key: "product_no", label: "품번", width: "w-[120px]" },
{ key: "product_type_name", label: "제품구분", width: "w-[90px]", align: "center" },
{ key: "nation_name", label: "국내/해외", width: "w-[90px]", align: "center" },
{ key: "receipt_date", label: "접수일", width: "w-[110px]", align: "center" },
{ key: "customer_request", label: "고객사요청사항", width: "w-[180px]" },
{ key: "cu01_cnt", label: "주문서첨부", width: "w-[90px]", align: "center", renderType: "clip" },
{ key: "shipping_method", label: "출하방법", width: "w-[90px]", align: "center" },
{ key: "manager_name", label: "담당자", width: "w-[100px]", align: "center" },
{ key: "incoterms", label: "인도조건", width: "w-[90px]", align: "center" },
{ key: "has_transaction_statement", label: "거래명세서", width: "w-[100px]", align: "center" }, { key: "has_transaction_statement", label: "거래명세서", width: "w-[100px]", align: "center" },
]; ];
+7
View File
@@ -53,7 +53,14 @@ export interface SaleListRow {
sales_status: string; sales_status: string;
production_status: string | null; production_status: string | null;
payment_type: string | null; payment_type: string | null;
payment_type_name: string | null;
nation: string | null; nation: string | null;
nation_name: string | null;
product_type_name: string | null;
receipt_date: string | null;
customer_request: string | null;
manager_name: string | null;
cu01_cnt: number | null;
serial_no: string | null; serial_no: string | null;
} }
+76
View File
@@ -0,0 +1,76 @@
-- ============================================================
-- 판매관리 자동 검증 SQL (BEGIN/ROLLBACK 시나리오)
-- 메인 테이블: project_mgmt + sales_registration + contract_mgmt
-- ============================================================
\echo ''
\echo '================== Sale 검증 시작 =================='
-- ─────────────────────────────────────────────────────────
-- [0] 사전 카운트
-- ─────────────────────────────────────────────────────────
\echo ''
\echo '[0] 사전 카운트 ============================'
SELECT 'project_mgmt' AS tbl, COUNT(*) FROM project_mgmt
UNION ALL SELECT 'sales_registration', COUNT(*) FROM sales_registration
UNION ALL SELECT 'project_mgmt matched by SR.project_no LIKE',
COUNT(DISTINCT T.project_no)
FROM project_mgmt T JOIN sales_registration SR ON SR.project_no LIKE T.project_no || '%';
-- ─────────────────────────────────────────────────────────
-- [1] 그리드 V1 신규 8개 컬럼 정합성 검증
-- ─────────────────────────────────────────────────────────
\echo ''
\echo '[1] 그리드 V1 신규 컬럼 (제품구분/국내해외/접수일/고객사요청사항/주문서첨부/출하방법/담당자/인도조건) =========================='
SELECT
T.project_no,
CC_PRD.code_name AS product_type_name,
CC_AREA.code_name AS nation_name,
CM.receipt_date,
COALESCE(NULLIF(CM.customer_request, ''), '-') AS customer_request,
COALESCE(AF.cu01_cnt, 0) AS cu01_cnt,
SR.shipping_method,
U_MGR.user_name AS manager_name,
SR.incoterms
FROM project_mgmt T
LEFT JOIN contract_mgmt CM ON CM.objid = T.contract_objid
LEFT JOIN sales_registration SR ON SR.project_no = T.project_no
LEFT JOIN user_info U_MGR ON U_MGR.user_id = SR.manager_user_id
LEFT JOIN comm_code CC_AREA ON CC_AREA.code_id = T.area_cd AND CC_AREA.status='active'
LEFT JOIN comm_code CC_PRD ON CC_PRD.code_id = T.product AND CC_PRD.status='active'
LEFT JOIN (
SELECT target_objid,
COUNT(*) FILTER (WHERE doc_type IN ('FTC_ORDER','ORDER')) AS cu01_cnt
FROM attach_file_info WHERE UPPER(status)='ACTIVE'
GROUP BY target_objid
) AF ON AF.target_objid = T.contract_objid
ORDER BY T.regdate DESC NULLS LAST, T.project_no DESC
LIMIT 10;
-- ─────────────────────────────────────────────────────────
-- [2] 판매상태 wace 로직 검증 (미판매/완판/분할판매)
-- ─────────────────────────────────────────────────────────
\echo ''
\echo '[2] 판매상태 wace 로직 (미판매/완판/분할판매) =========================='
SELECT
T.project_no,
T.quantity AS order_qty,
COALESCE(SR_AGG.sales_qty_sum, 0) AS sales_qty_sum,
CASE
WHEN COALESCE(SR_AGG.sales_qty_sum, 0) = 0 THEN '미판매'
WHEN COALESCE(SR_AGG.sales_qty_sum, 0) >= COALESCE(CAST(NULLIF(REPLACE(T.quantity, ',', ''), '') AS NUMERIC), 0) THEN '완판'
WHEN COALESCE(SR_AGG.sales_qty_sum, 0) > 0 THEN '분할판매'
ELSE ''
END AS sales_status
FROM project_mgmt T
LEFT JOIN (
SELECT SR2.project_no, SUM(SR2.sales_quantity) AS sales_qty_sum
FROM sales_registration SR2
GROUP BY SR2.project_no
) SR_AGG ON SR_AGG.project_no LIKE T.project_no || '%'
ORDER BY T.regdate DESC NULLS LAST
LIMIT 10;
\echo ''
\echo '================== Sale 검증 끝 =================='