diff --git a/backend-node/src/services/salesOrderMgmtService.ts b/backend-node/src/services/salesOrderMgmtService.ts index 0fe36bec..39f76246 100644 --- a/backend-node/src/services/salesOrderMgmtService.ts +++ b/backend-node/src/services/salesOrderMgmtService.ts @@ -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 diff --git a/docs/migration/sales/02-order-verify.md b/docs/migration/sales/02-order-verify.md new file mode 100644 index 00000000..05500cad --- /dev/null +++ b/docs/migration/sales/02-order-verify.md @@ -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` 기반 그리드. 메뉴 단위 사이클 동일 패턴으로 진행. diff --git a/frontend/app/(main)/COMPANY_16/sales/order/page.tsx b/frontend/app/(main)/COMPANY_16/sales/order/page.tsx index 8972b040..ac63fbe0 100644 --- a/frontend/app/(main)/COMPANY_16/sales/order/page.tsx +++ b/frontend/app/(main)/COMPANY_16/sales/order/page.tsx @@ -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 = { 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() {