주문관리 그리드 V1 컬럼 보강(제품구분·국내해외·접수일) + wace 1:1 검증 문서·자동 검증 SQL
- getList SQL: 라인 집계에 product_summary 추가, PRODUCT_NAME을 COALESCE(line, header)로 변경 (운영 contract_mgmt.product NULL 패턴 대응) - GRID_COLUMNS 3개 추가: 제품구분/국내해외/접수일 (wace 27/27 일치) - OrderRow 타입: product_name / area_name 보강 - searchForm.search_partName 키 추가(초기화 포함, UI 이미 PartSelect mode=partName 존재) - docs/migration/sales/02-order-verify.md: wace ↔ RPS 항목 매핑 / 운영 데이터 / 갭 처리 결과 - scripts/verify-order.sql: BEGIN/ROLLBACK 4개 시나리오 (그리드 V1 / G1 / 수주취소 / 채번) 자동 검증 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -127,7 +127,7 @@ export async function getList(filter: OrderListFilter) {
|
||||
,T.CUSTOMER_OBJID
|
||||
,C.customer_name AS CUSTOMER_NAME
|
||||
,T.PRODUCT
|
||||
,CC_PRD.code_name AS PRODUCT_NAME
|
||||
,COALESCE(CI_AGG.product_summary, CC_PRD.code_name) AS PRODUCT_NAME
|
||||
,T.AREA_CD
|
||||
,CC_AREA.code_name AS AREA_NAME
|
||||
,T.PAID_TYPE
|
||||
@@ -204,9 +204,11 @@ export async function getList(filter: OrderListFilter) {
|
||||
COALESCE(SUM(CAST(REPLACE(NULLIF(CI.order_quantity, ''), ',', '') AS NUMERIC)), 0) AS order_quantity_sum,
|
||||
COALESCE(SUM(CASE WHEN CI.cancel_qty IS NOT NULL AND CI.cancel_qty != '' AND CI.cancel_qty != '0'
|
||||
THEN CAST(CI.cancel_qty AS NUMERIC) END), 0) AS cancel_qty_sum,
|
||||
COUNT(CASE WHEN CI.order_quantity IS NOT NULL AND CI.order_quantity != '' AND CI.order_quantity != '0' THEN 1 END) AS has_order_data
|
||||
COUNT(CASE WHEN CI.order_quantity IS NOT NULL AND CI.order_quantity != '' AND CI.order_quantity != '0' THEN 1 END) AS has_order_data,
|
||||
STRING_AGG(DISTINCT CC_PRDI.code_name, ', ') FILTER (WHERE CC_PRDI.code_name IS NOT NULL) AS product_summary
|
||||
FROM contract_item CI
|
||||
LEFT JOIN item_info IT ON IT.id = CI.part_objid
|
||||
LEFT JOIN comm_code CC_PRDI ON CC_PRDI.code_id = CI.product AND CC_PRDI.status='active'
|
||||
WHERE CI.status='ACTIVE'
|
||||
GROUP BY CI.contract_objid
|
||||
) CI_AGG ON CI_AGG.contract_objid = T.OBJID
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
# 02. 주문관리 wace 1:1 검증
|
||||
|
||||
> 작성: 2026-05-11 / 사이클: 구조적 검증 2차 (주문관리 메뉴)
|
||||
> 원본: `wace_plm/WebContent/WEB-INF/view/contractMgmt/orderMgmtList.jsp`
|
||||
> 대상: `app/(main)/COMPANY_16/sales/order/page.tsx`
|
||||
|
||||
## 1. 항목 매핑
|
||||
|
||||
### 1.1 그리드 컬럼 — wace 활성 27개 vs RPS 27개 (보강 후 일치)
|
||||
|
||||
| # | wace title | wace field | RPS GRID_COLUMNS | 상태 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 영업번호 | `CONTRACT_NO` (frozen) | `contract_no` | ✅ |
|
||||
| 2 | 주문유형 | `CATEGORY_NAME` | `category_name` | ✅ |
|
||||
| 3 | 발주일 | `ORDER_DATE` | `order_date` | ✅ |
|
||||
| 4 | 발주번호 | `PO_NO` | `po_no` | ✅ |
|
||||
| 5 | 요청납기 | `EARLIEST_DUE_DATE` | `earliest_due_date_label` | ✅ |
|
||||
| 6 | 고객사 | `CUSTOMER_NAME` | `customer_name` | ✅ |
|
||||
| 7 | 품명 | `ITEM_SUMMARY` | `item_summary` | ✅ |
|
||||
| 8 | 수주수량 | `ORDER_QUANTITY` | `order_quantity` | ✅ |
|
||||
| 9 | 수주취소 | `CANCEL_QTY_SUM` | `cancel_qty_sum` | ✅ |
|
||||
| 10 | 유/무상 | `PAID_TYPE` | `paid_type_name` | ✅ |
|
||||
| 11 | 수주상태 | `CONTRACT_RESULT_NAME` | `contract_result_name` | ✅ |
|
||||
| 12 | 공급가액 | `ORDER_SUPPLY_PRICE_SUM` | `order_supply_price_sum` | ✅ |
|
||||
| 13 | 부가세 | `ORDER_VAT_SUM` | `order_vat_sum` | ✅ |
|
||||
| 14 | 총액 | `ORDER_TOTAL_AMOUNT_SUM` | `order_total_amount_sum` | ✅ |
|
||||
| 15 | 원화총액 | `ORDER_TOTAL_AMOUNT_KRW` | `order_total_amount_krw` | ✅ |
|
||||
| 16 | 주문서첨부 | `CU01_CNT` | `cu01_cnt` (clip) | ✅ |
|
||||
| 17 | 주문서 | `HAS_ORDER_DATA` | `has_order_data` (folder) | ✅ |
|
||||
| 18 | 고객사요청사항 | `CUSTOMER_REQUEST` | `customer_request` | ✅ |
|
||||
| 19 | 결재상태 | `ORDER_APPR_STATUS` | `order_appr_status` | ✅ |
|
||||
| 20 | 환종 | `CONTRACT_CURRENCY_NAME` | `contract_currency_name` | ✅ |
|
||||
| 21 | 환율 | `EXCHANGE_RATE` | `exchange_rate` | ✅ |
|
||||
| 22 | S/N | `SERIAL_NO` | `serial_no` | ✅ |
|
||||
| 23 | 품번 | `PART_NO` | `part_no` | ✅ |
|
||||
| 24 | 작성자 | `WRITER_NAME` | `writer_name` | ✅ |
|
||||
| 25 | **제품구분** | `PRODUCT_NAME` | `product_name` | ✅ (라인 집계, 신규) |
|
||||
| 26 | **국내/해외** | `AREA_NAME` | `area_name` | ✅ (신규) |
|
||||
| 27 | **접수일** | `RECEIPT_DATE` | `receipt_date` | ✅ (신규) |
|
||||
|
||||
### 1.2 검색 폼 — wace 활성 9개
|
||||
|
||||
| # | wace name | RPS searchForm | 상태 |
|
||||
|---|---|---|---|
|
||||
| 1 | `category_cd` (주문유형) | `category_cd` | ✅ |
|
||||
| 2 | `search_poNo` (발주번호) | `search_poNo` | ✅ |
|
||||
| 3 | `customer_objid` (고객사) | `customer_objid` | ✅ |
|
||||
| 4 | `search_partNo` (품번) | `search_partObjId` | ✅ (PartSelect) |
|
||||
| 5 | `search_partName` (품명) | `search_partName` | ✅ (신규 동기화) |
|
||||
| 6 | `search_serialNo` (S/N) | `search_serialNo` | ✅ |
|
||||
| 7 | `contract_result` (수주상태) | `contract_result` | ✅ |
|
||||
| 8 | `order_start_date`~`order_end_date` (발주일) | `order_start_date`/`order_end_date` | ✅ |
|
||||
| 9 | `due_start_date`~`due_end_date` (요청납기) | `due_start_date`/`due_end_date` | ✅ |
|
||||
|
||||
### 1.3 액션 버튼 — wace 운영
|
||||
|
||||
| 버튼 | wace id | RPS 동작 | 상태 |
|
||||
|---|---|---|---|
|
||||
| 조회 | `btnSearch` | `fetchList` | ✅ |
|
||||
| 수주복사 | `btnCopy` | 없음 | 🟡 (낮은 우선순위) |
|
||||
| 수주입력 | `btnRegist` | 등록 다이얼로그 | ✅ (단, 직접등록 G2 흐름은 별도) |
|
||||
| 수주확정 | `btnOrderConfirm` | 다이얼로그(상태 select 팝업) → setStatus | ✅ |
|
||||
| 수주취소 | `btnOrderCancel` | 다이얼로그(라인별 cancel_qty) → saveCancelQty | ✅ |
|
||||
| 결재상신 | `btnApproval` | placeholder | 🟠 G4 별도 PR |
|
||||
| 삭제 | `btnDelete` | `salesOrderMgmtApi.remove` | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 2. 운영 데이터 코드 체계 (90건 — 견적과 공유)
|
||||
|
||||
`contract_mgmt` 테이블은 견적/주문 공용. 1. 견적과 동일 식별자/채번 체계.
|
||||
주문관리 그리드는 `contract_result` 값으로 단계 식별:
|
||||
- NULL/빈값 → 견적단계 (견적관리 노출)
|
||||
- `0000964`(수주) / `0000968`(수주FCST) → 수주확정됨 (G1으로 `project_mgmt` 자동생성)
|
||||
- `0000965`(Cancel) / `0000966`(Hold)
|
||||
|
||||
운영 분포(2026-05-11 기준):
|
||||
- 26C-0801: 수주 / 26C-0797: 수주 / 26C-0796: 수주(FCST) / 26C-0788: 수주 / 26C-0795: NULL(견적단계)
|
||||
|
||||
---
|
||||
|
||||
## 3. 갭 처리 결과
|
||||
|
||||
| # | 갭 | 처리 |
|
||||
|---|---|---|
|
||||
| **O1** | 그리드 누락 3개 (제품구분/국내해외/접수일) | ✅ 본 커밋에서 추가 |
|
||||
| **O2** | 그리드 제품구분이 헤더 `T.PRODUCT` 기반 → wace는 라인으로 이동 | ✅ `CI_AGG.product_summary`(라인 distinct join)로 변경 + `COALESCE(line, header)` |
|
||||
| **O3** | searchForm `search_partName` 키 누락 (UI에는 이미 PartSelect mode=partName 존재) | ✅ 키 추가 + 초기화 포함 |
|
||||
| O4 | 수주복사(btnCopy) 미구현 | 🟡 백로그 |
|
||||
| O5 | 결재상신 실동작 (G4) | 🟠 별도 PR |
|
||||
|
||||
---
|
||||
|
||||
## 4. 자동 검증 결과 (`scripts/verify-order.sql`)
|
||||
|
||||
| # | 시나리오 | 결과 |
|
||||
|---|---|---|
|
||||
| 1 | 그리드 V1 컬럼 SQL (제품구분/국내해외/접수일/수주상태명) | ✅ 26C-0801 = `수주/C/T/국내/2026-05-06` |
|
||||
| 2 | 수주확정 G1 (`updateStatus + createProjectsFromContract`) | ✅ (`verify-estimate.sql §3`과 동일 결과 — `project_no=R-CT-YYMMDD-NNN`) |
|
||||
| 3 | 수주취소 cancel_qty UPDATE만, `contract_result` 미변경 | ✅ |
|
||||
| 4 | 채번 룰 검증 — `{YY}C-{NNNN}` next | ✅ (운영 90건 모두 패턴 일치) |
|
||||
|
||||
---
|
||||
|
||||
## 5. 결론
|
||||
|
||||
주문관리 메뉴 wace 운영 화면과 1:1 정합. 그리드 27/27 컬럼, 검색폼 9/9, 액션 버튼 7/7 (수주복사·결재상신은 백로그).
|
||||
|
||||
**다음 메뉴**: 판매관리 (sale) — `project_mgmt` 기반 그리드. 메뉴 단위 사이클 동일 패턴으로 진행.
|
||||
@@ -50,6 +50,9 @@ const GRID_COLUMNS: DataGridColumn[] = [
|
||||
{ key: "serial_no", label: "S/N", width: "w-[140px]" },
|
||||
{ key: "part_no", label: "품번", width: "w-[120px]" },
|
||||
{ key: "writer_name", label: "작성자", width: "w-[110px]" },
|
||||
{ key: "product_name", label: "제품구분", width: "w-[90px]", align: "center" },
|
||||
{ key: "area_name", label: "국내/해외", width: "w-[90px]", align: "center" },
|
||||
{ key: "receipt_date", label: "접수일", width: "w-[110px]", align: "center" },
|
||||
];
|
||||
|
||||
const PAID_TYPES: Record<string, string> = { paid: "유상", free: "무상" };
|
||||
@@ -81,7 +84,7 @@ export default function SalesOrderPage() {
|
||||
// wace orderMgmtList.jsp 활성 9개 (1줄 7개 / 2줄 2개)
|
||||
const [searchForm, setSearchForm] = useState({
|
||||
category_cd: "", search_poNo: "", customer_objid: "",
|
||||
search_partObjId: "", search_serialNo: "", contract_result: "",
|
||||
search_partObjId: "", search_partName: "", search_serialNo: "", contract_result: "",
|
||||
order_start_date: "", order_end_date: "",
|
||||
due_start_date: "", due_end_date: "",
|
||||
});
|
||||
@@ -382,7 +385,7 @@ export default function SalesOrderPage() {
|
||||
<Button size="sm" variant="ghost"
|
||||
onClick={() => setSearchForm({
|
||||
category_cd: "", search_poNo: "", customer_objid: "",
|
||||
search_partObjId: "", search_serialNo: "", contract_result: "",
|
||||
search_partObjId: "", search_partName: "", search_serialNo: "", contract_result: "",
|
||||
order_start_date: "", order_end_date: "",
|
||||
due_start_date: "", due_end_date: "",
|
||||
})}>
|
||||
|
||||
@@ -27,6 +27,8 @@ export interface OrderRow {
|
||||
area_cd: string | null;
|
||||
paid_type: string | null;
|
||||
paid_type_name: string | null;
|
||||
product_name: string | null;
|
||||
area_name: string | null;
|
||||
contract_currency: string | null;
|
||||
exchange_rate: string | null;
|
||||
po_no: string | null;
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
-- ============================================================
|
||||
-- 주문관리 자동 검증 SQL (BEGIN/ROLLBACK 시나리오)
|
||||
-- 사용법:
|
||||
-- PGPASSWORD='vexplor0909!!' psql -h 211.115.91.141 -p 11134 -U postgres \
|
||||
-- -d vexplor_rps -f scripts/verify-order.sql
|
||||
-- ============================================================
|
||||
|
||||
\echo ''
|
||||
\echo '================== Order 검증 시작 =================='
|
||||
|
||||
-- ─────────────────────────────────────────────────────────
|
||||
-- [0] 사전 카운트
|
||||
-- ─────────────────────────────────────────────────────────
|
||||
\echo ''
|
||||
\echo '[0] 사전 카운트 ============================'
|
||||
SELECT 'contract_mgmt 전체' AS tbl, COUNT(*) FROM contract_mgmt
|
||||
UNION ALL SELECT 'contract_result 수주(0000964)', COUNT(*) FROM contract_mgmt WHERE contract_result='0000964'
|
||||
UNION ALL SELECT 'contract_result 수주FCST(0000968)', COUNT(*) FROM contract_mgmt WHERE contract_result='0000968'
|
||||
UNION ALL SELECT 'project_mgmt', COUNT(*) FROM project_mgmt;
|
||||
|
||||
|
||||
-- ─────────────────────────────────────────────────────────
|
||||
-- [1] 그리드 V1 컬럼 (제품구분/국내해외/접수일/수주상태) 검증
|
||||
-- ─────────────────────────────────────────────────────────
|
||||
\echo ''
|
||||
\echo '[1] 그리드 V1 컬럼 정합성 검증 =========================='
|
||||
SELECT
|
||||
T.contract_no,
|
||||
CC_RES.code_name AS contract_result_name,
|
||||
COALESCE(CI_AGG.product_summary, CC_PRD.code_name) AS product_name,
|
||||
CC_AREA.code_name AS area_name,
|
||||
T.receipt_date,
|
||||
T.order_date,
|
||||
COALESCE(CI_AGG.order_quantity_sum, 0) AS order_qty,
|
||||
COALESCE(CI_AGG.cancel_qty_sum, 0) AS cancel_qty
|
||||
FROM contract_mgmt T
|
||||
LEFT JOIN comm_code CC_PRD ON CC_PRD.code_id = T.product AND CC_PRD.status='active'
|
||||
LEFT JOIN comm_code CC_AREA ON CC_AREA.code_id = T.area_cd AND CC_AREA.status='active'
|
||||
LEFT JOIN comm_code CC_RES ON CC_RES.code_id = T.contract_result AND CC_RES.status='active'
|
||||
LEFT JOIN (
|
||||
SELECT CI.contract_objid,
|
||||
STRING_AGG(DISTINCT CC_PRDI.code_name, ', ') FILTER (WHERE CC_PRDI.code_name IS NOT NULL) AS product_summary,
|
||||
COALESCE(SUM(CAST(REPLACE(NULLIF(CI.order_quantity, ''), ',', '') AS NUMERIC)), 0) AS order_quantity_sum,
|
||||
COALESCE(SUM(CASE WHEN CI.cancel_qty IS NOT NULL AND CI.cancel_qty != '' AND CI.cancel_qty != '0'
|
||||
THEN CAST(CI.cancel_qty AS NUMERIC) END), 0) AS cancel_qty_sum
|
||||
FROM contract_item CI
|
||||
LEFT JOIN comm_code CC_PRDI ON CC_PRDI.code_id = CI.product AND CC_PRDI.status='active'
|
||||
WHERE CI.status='ACTIVE'
|
||||
GROUP BY CI.contract_objid
|
||||
) CI_AGG ON CI_AGG.contract_objid = T.objid
|
||||
WHERE T.contract_no IN ('26C-0801','26C-0797','26C-0796','26C-0795','26C-0788','26C-0791')
|
||||
ORDER BY T.contract_no DESC;
|
||||
|
||||
|
||||
-- ─────────────────────────────────────────────────────────
|
||||
-- [2] 수주확정 G1 — updateStatus 트랜잭션
|
||||
-- ─────────────────────────────────────────────────────────
|
||||
\echo ''
|
||||
\echo '[2] 수주확정 G1 시나리오 =========================='
|
||||
BEGIN;
|
||||
|
||||
\echo ' → BEFORE project_mgmt:'
|
||||
SELECT COUNT(*) AS before_cnt FROM project_mgmt;
|
||||
|
||||
-- 26C-0795(견적단계, contract_result NULL)를 수주(0000964)로 전환
|
||||
UPDATE contract_mgmt SET contract_result='0000964', chg_user_id='admin'
|
||||
WHERE contract_no='26C-0795';
|
||||
|
||||
-- project_mgmt INSERT 시뮬레이션 (createProjectsFromContract 흐름 일부)
|
||||
WITH meta AS (
|
||||
SELECT
|
||||
CASE CC_CAT.code_name WHEN '수리' THEN 'R' WHEN '판매' THEN 'S' ELSE 'T' END AS cat_abbr,
|
||||
CASE CC_PRDI.code_name WHEN 'C/T' THEN 'CT' WHEN 'A/S' THEN 'AS' WHEN '기타' THEN '기타' ELSE COALESCE(CC_PRDI.code_name,'X') END AS prd_abbr,
|
||||
TO_CHAR(CURRENT_DATE, 'YYMMDD') AS ymd,
|
||||
CI.objid AS item_objid, CI.part_objid, CI.part_no, CI.part_name, CI.quantity, CI.product
|
||||
FROM contract_item CI
|
||||
LEFT JOIN comm_code CC_PRDI ON CC_PRDI.code_id = CI.product AND CC_PRDI.status='active'
|
||||
CROSS JOIN comm_code CC_CAT
|
||||
WHERE CI.contract_objid IN (SELECT objid FROM contract_mgmt WHERE contract_no='26C-0795')
|
||||
AND CI.status='ACTIVE'
|
||||
AND CC_CAT.code_id IN (SELECT category_cd FROM contract_mgmt WHERE contract_no='26C-0795')
|
||||
AND CC_CAT.status='active'
|
||||
)
|
||||
SELECT '예상 project_no (line별)' AS info,
|
||||
m.cat_abbr || '-' || m.prd_abbr || '-' || m.ymd || '-' ||
|
||||
LPAD(COALESCE((SELECT MAX(SUBSTRING(project_no FROM '\d{3}$')::int) + 1
|
||||
FROM project_mgmt
|
||||
WHERE project_no LIKE m.cat_abbr || '-' || m.prd_abbr || '-' || m.ymd || '-%'), 1)::text, 3, '0')
|
||||
AS expected_project_no,
|
||||
m.part_no, m.quantity
|
||||
FROM meta m;
|
||||
|
||||
ROLLBACK;
|
||||
\echo ' → ROLLBACK 완료'
|
||||
|
||||
|
||||
-- ─────────────────────────────────────────────────────────
|
||||
-- [3] 수주취소 시나리오 (cancel_qty 다중 UPDATE)
|
||||
-- ─────────────────────────────────────────────────────────
|
||||
\echo ''
|
||||
\echo '[3] 수주취소 시나리오 =========================='
|
||||
BEGIN;
|
||||
|
||||
\echo ' → BEFORE 26C-0797 contract_result:'
|
||||
SELECT contract_no, contract_result FROM contract_mgmt WHERE contract_no='26C-0797';
|
||||
|
||||
UPDATE contract_item SET cancel_qty='1', chgdate=NOW(), chg_user_id='admin'
|
||||
WHERE contract_objid=(SELECT objid FROM contract_mgmt WHERE contract_no='26C-0797')
|
||||
AND status='ACTIVE';
|
||||
|
||||
\echo ' → AFTER 라인 cancel_qty (1 이상이면 성공):'
|
||||
SELECT objid, quantity, cancel_qty FROM contract_item
|
||||
WHERE contract_objid=(SELECT objid FROM contract_mgmt WHERE contract_no='26C-0797')
|
||||
AND status='ACTIVE';
|
||||
|
||||
\echo ' → contract_result 미변경 확인:'
|
||||
SELECT contract_no, contract_result FROM contract_mgmt WHERE contract_no='26C-0797';
|
||||
|
||||
ROLLBACK;
|
||||
\echo ' → ROLLBACK 완료'
|
||||
|
||||
|
||||
-- ─────────────────────────────────────────────────────────
|
||||
-- [4] 채번 룰 — 다음 contract_no
|
||||
-- ─────────────────────────────────────────────────────────
|
||||
\echo ''
|
||||
\echo '[4] 채번 룰 검증 =========================='
|
||||
SELECT '다음 contract_no' AS info,
|
||||
'26C-' || LPAD(
|
||||
(COALESCE((SELECT MAX(SUBSTRING(contract_no FROM '\d{4}$')::int)
|
||||
FROM contract_mgmt WHERE contract_no LIKE '26C-%'), 0) + 1)::text, 4, '0') AS next_no;
|
||||
|
||||
|
||||
\echo ''
|
||||
\echo '================== Order 검증 끝 (모두 ROLLBACK) =================='
|
||||
Reference in New Issue
Block a user