diff --git a/docs/migration/sales/09-purchase-request.md b/docs/migration/sales/09-purchase-request.md new file mode 100644 index 00000000..71342799 --- /dev/null +++ b/docs/migration/sales/09-purchase-request.md @@ -0,0 +1,136 @@ +# 영업관리 > 구매요청서관리 · 품의서관리 (2026-05-15 신규 2메뉴) + +> 작성: 2026-05-15 / 작성자: hjjeong +> 원본: wace_plm `/salesMng/purchaseRequestRegList.do` + `/salesMng/purchaseRegProposalMngList.do` +> 신규 위치: `frontend/app/(main)/COMPANY_16/sales/{purchase-request,purchase-proposal}/page.tsx` +> 백엔드: `backend-node/src/{services/salesPurchaseRequestService.ts,routes/salesPurchaseRequestRoutes.ts}` + +## 0. 한 문장 요약 + +`sales_request_master` 한 테이블을 `doc_type` 으로 갈라 **구매요청서(`PURCHASE_REG`)** → **품의서(`PURCHASE_REG_PROPOSAL`)** → 결재완료 시 **구매관리>품의서관리**로 흘러가는 3단 파이프라인의 앞단 2단을 영업관리 메뉴에 신설. + +## 1. 메뉴 매핑 + +| # | 메뉴명 | RPS URL | wace URL | wace JSP | wace 매퍼 | DOC_TYPE 필터 | +|---|---|---|---|---|---|---| +| 5 | 구매요청서관리 | `/purchase-request/request` | `/salesMng/purchaseRequestRegList.do` | `salesMng/purchaseRequestRegList.jsp` (728줄) | `salesMng.getSalesRequestMasterGridList` (DOC_TYPE_FILTER='PURCHASE_REG') | `PURCHASE_REG` | +| 6 | 품의서관리 (영업) | `/purchase-request/proposal` | `/salesMng/purchaseRegProposalMngList.do` | `salesMng/purchaseRegProposalMngList.jsp` (313줄) | `salesMng.getPurchaseRegProposalMngGridList` | `PURCHASE_REG_PROPOSAL` | + +## 2. 구매관리 > 품의서관리와의 차이 (중요) + +| 구분 | 구매관리 > 품의서관리 (`/purchase/proposal`) | 영업관리 > 품의서관리 (`/purchase-request/proposal`) | +|---|---|---| +| wace URL | `/salesMng/proposalMngList.do` | `/salesMng/purchaseRegProposalMngList.do` | +| wace 매퍼 | `getProposalMngGridList` | `getPurchaseRegProposalMngGridList` | +| DOC_TYPE | `PROPOSAL` 전부 + `PURCHASE_REG_PROPOSAL`(결재완료만) | `PURCHASE_REG_PROPOSAL` 전용 | +| 화면 의미 | **발주서 생성 풀** — 결재완료된 품의서 모음 | **결재상신 화면** — 구매요청서에서 만든 품의서 | +| 컬럼 차이 | + 발주서 No, + 공급업체, + 총액 | 헤더 9개 (품의서No/프로젝트/구매유형/주문유형/제품구분/품번/품명/결재상태/작성일/작성자) | +| 액션 | 결재상신 / **발주서생성** | **결재상신** (Amaranth10 SSO) | +| 데이터 흐름 | 영업관리>품의서 결재완료 → 자동 노출 | 구매요청서에서 품의서생성 시 신규 row | + +**핵심**: 같은 `sales_request_master` 테이블이지만 `doc_type` 으로 책임이 갈림. 영업관리 측은 작성·결재상신을 담당, 구매관리 측은 결재완료된 것들을 발주서로 변환. + +## 3. 3단 파이프라인 흐름 + +``` +[구매요청서관리] [영업>품의서관리] [구매>품의서관리] +PURCHASE_REG PURCHASE_REG_PROPOSAL PURCHASE_REG_PROPOSAL +sales_request_master ──┬─── sales_request_master ──┬─── (결재완료만 노출) +PROJECT_NO=프로젝트 │ PROJECT_NO=부모 PURCHASE_REG │ + ++ 작성중/품의서생성/확정 │ OBJID │ PROPOSAL 전부 + │ │ + 품의서생성 결재상신 발주서생성 + (선택 1건 → POST) (Amaranth SSO) (purchase_order_*) +``` + +부모-자식 연결: `PURCHASE_REG_PROPOSAL.PROJECT_NO = PURCHASE_REG.OBJID::VARCHAR` (wace 매퍼 1138줄 CASE 절 1:1). + +## 4. 그리드 컬럼 매핑 + +### 4.1 구매요청서관리 (14컬럼) + +| # | 라벨 | 필드 | 출처 | +|---|---|---|---| +| 1 | 요청번호 | `request_mng_no` | SRM.REQUEST_MNG_NO | +| 2 | 구매유형 | `purchase_type_name` | comm_code(0001814) | +| 3 | 프로젝트번호 | `project_number` | project_mgmt.PROJECT_NO | +| 4 | 주문유형 | `order_type_name` | project_mgmt.CATEGORY_CD 우선, fallback SRM.ORDER_TYPE | +| 5 | 제품구분 | `product_name_full` | contract_mgmt.PRODUCT 우선, fallback SRM.PRODUCT_NAME | +| 6 | 고객사 | `customer_name` | contract_mgmt.CUSTOMER_OBJID → CLIENT_MNG/SUPPLY_MNG 분기 | +| 7 | 유/무상 | `paid_type_name` | contract_mgmt.PAID_TYPE 우선, fallback SRM.PAID_TYPE | +| 8 | 품번 | `part_display` | MBOM_DETAIL → PART_MNG.PART_NO (+ "외 N건") | +| 9 | 품명 | `part_name_display` | MBOM_DETAIL → PART_MNG.PART_NAME (+ "외 N건") | +| 10 | 구매요청서 | `has_purchase_request_label` | MBOM_HEADER_OBJID 존재 시 "작성" else "미작성" | +| 11 | 작성자 | `request_user_name` | user_name(REQUEST_USER_ID ?? WRITER) | +| 12 | 입고요청일 | `delivery_request_date` | SRM.DELIVERY_REQUEST_DATE | +| 13 | 작성일 | `regdate_title` | TO_CHAR(SRM.REGDATE, 'YYYY-MM-DD') | +| 14 | 상태 | `status_title` | 품의서생성 / 확정 / 작성중 CASE | + +### 4.2 품의서관리 (영업) (10컬럼) + +| # | 라벨 | 필드 | 출처 | +|---|---|---|---| +| 1 | 품의서 No | `proposal_no` | SRM.REQUEST_MNG_NO | +| 2 | 프로젝트번호 | `project_number` | project_mgmt.PROJECT_NO | +| 3 | 구매유형 | `purchase_type_name` | comm_code(0001814) | +| 4 | 주문유형 | `order_type_name` | 위와 동일 | +| 5 | 제품구분 | `product_name_title` | comm_code(SRM.PRODUCT_NAME) | +| 6 | 품번 | `part_display` | MBOM_DETAIL → PART_MNG | +| 7 | 품명 | `part_name_display` | 위와 동일 | +| 8 | 결재상태 | `status_title` | Amaranth status 우선순위 (결재완료/결재 상신중/반려/등록중) | +| 9 | 작성일 | `regdate_title` | TO_CHAR(SRM.REGDATE) | +| 10 | 작성자 | `writer_name` | user_name(SRM.WRITER) | + +## 5. 백엔드 SQL 정합성 메모 + +### 5.1 상태 CASE (구매요청서) +```sql +CASE + WHEN EXISTS ( + SELECT 1 FROM SALES_REQUEST_MASTER P + WHERE P.DOC_TYPE = 'PURCHASE_REG_PROPOSAL' + AND P.PROJECT_NO = SRM.OBJID::VARCHAR + ) THEN '품의서생성' + WHEN SRM.STATUS = 'confirmed' THEN '확정' + WHEN SRM.STATUS = 'create' THEN '작성중' + ELSE COALESCE(SRM.STATUS, '') +END +``` +wace 매퍼 `salesMng.xml:1135~1141` 1:1. + +### 5.2 결재상태 CASE (품의서) +```sql +CASE + WHEN AMR.STATUS = 'complete' THEN 'approvalComplete' + WHEN AMR.STATUS = 'inProcess' THEN 'inProcess' + WHEN AMR.STATUS = 'reject' THEN 'reject' + ELSE SRM.STATUS +END +``` +JOIN: `amaranth_approval AMR ON SRM.OBJID::VARCHAR=AMR.TARGET_OBJID AND AMR.TARGET_TYPE='PROPOSAL'`. +wace 매퍼 `salesMng.xml:4805~4812` 1:1. + +### 5.3 sales_request_part 누락 처리 +wace 원본은 `SALES_REQUEST_PART` (구매요청 라인) 테이블을 사용해 품번/품명 집계. RPS 에는 미존재 → **MBOM_DETAIL → PART_MNG fallback** (구매관리 패턴 동일). 운영DB DDL 추출 후 전환 예정. 메모리 [feedback_missing_tables_workflow](../../../../.claude/projects/-Users-jhj-vexplor-rps/memory/feedback_missing_tables_workflow.md). + +## 6. 백로그 (placeholder 상태) + +| 우선 | 항목 | 원본 위치 | 권장 작업 | +|---|---|---|---| +| 🟠 | **구매요청서작성 다이얼로그** | wace `salesRequestFormPopUp.jsp` + `purchaseListFormPopUp.jsp` | M-BOM 선택 → sales_request_master INSERT (DOC_TYPE='PURCHASE_REG') + 라인 입력 (sales_request_part 신설 후) | +| 🟠 | **품의서생성** | wace `salesMng/createProposalFromPurchaseReg.do` + `getProposalTargetPartsFromPurchaseReg.do` | 선택된 구매요청서 → 단가+공급업체 입력된 품목만 필터 → 새 sales_request_master INSERT (DOC_TYPE='PURCHASE_REG_PROPOSAL', PROJECT_NO=원본 OBJID, STATUS='create') | +| 🟠 | **결재상신 (Amaranth SSO)** | wace `purchaseRegProposalMngList.jsp:75~99` + `getAmaranthSsoUrl` | target_type=`PROPOSAL`, formId=`1163`, compSeq=`1000`. 기존 견적/수주 결재상신 패턴(G11/G11E) 재사용. `AMARANTH_OUT_PROCESS_CODE_PROPOSAL` 환경변수 신설 | +| 🟡 | sales_request_part 신설 | 운영DB 211.115.91.141:11133 | DDL 추출 → `db/migrations/NNN_create_sales_request_part.sql` | + +## 7. 1차 스캐폴드 커밋 + +`7e7c6a0a` 영업관리 구매요청서관리·품의서관리 신규 2메뉴 (wace_plm 1:1) +- 6 files, +804 insertions + +## 8. 검증 체크리스트 + +- [x] 백엔드/프론트 타입체크 통과 (사전부터 있던 multilang/zebraBluetooth 외 새 파일 0 에러) +- [ ] dev 서버 (8080/9771) 에서 두 메뉴 그리드 조회 동작 확인 +- [ ] sales_request_master 에 `DOC_TYPE='PURCHASE_REG'` / `'PURCHASE_REG_PROPOSAL'` 행 존재 시 노출 확인 (현재 데이터 0건일 수 있음) +- [ ] 검색 필터 (구매유형/작성자/제품구분/작성일) 동작 +- [ ] 구매관리>품의서관리 와 영업관리>품의서관리 가 동일 row 를 doc_type 기준으로 다르게 보여주는지 회귀 확인 diff --git a/docs/migration/sales/README.md b/docs/migration/sales/README.md index b67ba769..f3df8748 100644 --- a/docs/migration/sales/README.md +++ b/docs/migration/sales/README.md @@ -9,11 +9,13 @@ - **이식 방식**: JSP → Next.js 리라이트 (백엔드도 Java→Node 재작성, vexplor_rps `backend-node` 패턴 채택) - **스키마 정책**: **하이브리드** — 도메인 테이블(`contract_mgmt`, `estimate_mgmt`, `sales_registration` 등)은 wace_plm 원본 스키마를 그대로 가져오고, 거래처/품목 등 **마스터는 vexplor_rps 기존 테이블(`customer_mng`, `item_info`)에 매핑** - **관리자 메뉴**: 이식 대상 아님 (vexplor 그대로 사용) -- **이식 대상 메뉴 4개**: +- **이식 대상 메뉴 6개** (초기 4개 + 2026-05-15 확장 2개): 1. 견적관리 (`/contractMgmt/estimateList_new.do`) 2. 주문서관리 (`/contractMgmt/orderMgmtList.do`) 3. 판매관리 (`/contractMgmt/salesMgmtList.do` → SalesNcollect로 위임) 4. 매출관리 (`/revenueMgmt/revenueList.do`) + 5. **구매요청서관리** (`/salesMng/purchaseRequestRegList.do`) — 2026-05-15 추가 + 6. **품의서관리(영업)** (`/salesMng/purchaseRegProposalMngList.do`) — 2026-05-15 추가 ## 1. 메뉴 매핑표 @@ -23,6 +25,8 @@ | 2 | 주문서관리 | `/contractMgmt/orderMgmtList.do` | `contractMgmt/orderMgmtList.jsp` (45KB) | `ContractMgmtController` (2504–3169 line) / `ContractMgmtService` | `app/(main)/COMPANY_16/sales/order/page.tsx` (재작성) + `backend-node/src/{routes,services}/orderMgmtRoutes.ts` | [02-order.md](./02-order.md) | | 3 | 판매관리 | `/contractMgmt/salesMgmtList.do` (위임) → `/salesNcollectMgmt/sales.do` | `salesmgmt/salesMgmt/*.jsp` | `SalesNcollectMgmtController` (line 763~) / `SalesNcollectMgmtService` | `app/(main)/COMPANY_16/sales/sale/page.tsx` + `backend-node/src/{routes,services}/saleRoutes.ts` | [03-sale.md](./03-sale.md) | | 4 | 매출관리 | `/revenueMgmt/revenueList.do` | `salesmgmt/salesMgmt/revenueMgmtList.jsp` | `SalesNcollectMgmtController` (line 103, 214) / `SalesNcollectMgmtService` | `app/(main)/COMPANY_16/sales/revenue/page.tsx` + `backend-node/src/{routes,services}/revenueRoutes.ts` | [04-revenue.md](./04-revenue.md) | +| 5 | 구매요청서관리 | `/salesMng/purchaseRequestRegList.do` | `salesMng/purchaseRequestRegList.jsp` (728줄) | `SalesMngController` (210~258) / `salesMng.getSalesRequestMasterGridList` (DOC_TYPE_FILTER='PURCHASE_REG') | `app/(main)/COMPANY_16/purchase-request/request/page.tsx` + `backend-node/src/{routes,services}/salesPurchaseRequestRoutes.ts` | [09-purchase-request.md](./09-purchase-request.md) | +| 6 | 품의서관리 (영업) | `/salesMng/purchaseRegProposalMngList.do` | `salesMng/purchaseRegProposalMngList.jsp` (313줄) | `SalesMngController` (1363~1389) / `salesMng.getPurchaseRegProposalMngGridList` | `app/(main)/COMPANY_16/purchase-request/proposal/page.tsx` + `backend-node/src/{routes,services}/salesPurchaseRequestRoutes.ts` | [09-purchase-request.md](./09-purchase-request.md) | | ─ | 마스터 매핑 | (전 메뉴 공통) | — | — | — | [05-master-mapping.md](./05-master-mapping.md) | > ⚠️ vexplor_rps의 기존 [sales/quote](../../../frontend/app/(main)/COMPANY_16/sales/quote/page.tsx)/[sales/order](../../../frontend/app/(main)/COMPANY_16/sales/order/page.tsx) 페이지는 별도 도메인(`quote_mng`/`quote_detail`)으로 만들어져 있음. 이식 후 **사용 중지** 또는 **별도 모듈로 이름 변경** 검토 필요. 신규 페이지는 `estimate/`, `order/` 신규 경로로 작성하는 것을 권장. @@ -143,8 +147,9 @@ import { useAuth } from "@/hooks/useAuth"; 1. ~~운영 DB DDL 추출~~ 완료 (2026-05-07) 2. ~~01~04 상세 매핑 + 1차 이식~~ 완료 (2026-05-08) -3. **[00-gap.md](./00-gap.md) 우선** — 원본 흐름 10단계 vs 이식본 GAP 매트릭스. 다음 PR(A: 수주확정→프로젝트 자동생성, B: 직접등록 통합폼, C: 결재·메일·PDF) 합의 문서. -4. PR-A부터 착수: `salesOrderMgmtService.updateStatus`에 project_mgmt 자동생성 + project_no 채번 로직 이식. +3. ~~[00-gap.md](./00-gap.md) PR-A/B/C 흐름 + G7~G11 결재상신~~ 완료 (2026-05-11) +4. ~~구매요청서관리·품의서관리(영업) 1차 스캐폴드~~ 완료 (2026-05-15, 커밋 `7e7c6a0a`, [09-purchase-request.md](./09-purchase-request.md)) +5. **다음**: 구매요청서작성 다이얼로그 + 품의서생성 액션 + 영업>품의서 Amaranth 결재상신(target_type='PROPOSAL', formId='1163'). sales_request_part 운영DB DDL 추출 선행. ## 8. 공통 UX 규칙 (검색 폼 / 영업관리 4개 메뉴 동일 적용) diff --git a/frontend/app/(main)/COMPANY_16/sales/purchase-proposal/page.tsx b/frontend/app/(main)/COMPANY_16/purchase-request/proposal/page.tsx similarity index 97% rename from frontend/app/(main)/COMPANY_16/sales/purchase-proposal/page.tsx rename to frontend/app/(main)/COMPANY_16/purchase-request/proposal/page.tsx index f713bd58..3677bd29 100644 --- a/frontend/app/(main)/COMPANY_16/sales/purchase-proposal/page.tsx +++ b/frontend/app/(main)/COMPANY_16/purchase-request/proposal/page.tsx @@ -132,14 +132,13 @@ export default function PurchaseRegProposalPage() { if (!sel) return; if (sel.status === "inProcess") return toast.info("결재 진행중인 건은 상신할 수 없습니다."); if (sel.status === "approvalComplete") return toast.info("결재 완료된 건은 상신할 수 없습니다."); - toast.info("결재상신 — Amaranth10 SSO 연동 후 활성 (sales/purchase-proposal/:id/amaranth-approval)"); + toast.info("결재상신 기능은 준비 중입니다."); }; return (
}