# 영업관리 이식 (wace_plm → vexplor_rps) > 작성: 2026-05-07 / 작성자: hjjeong > 대상: vexplor_rps (RPS 전용 분기, COMPANY_16 단독 운영) > 원본: wace_plm (Java 7 / Spring 3.2.4 / JSP / MyBatis) ## 0. 정책 (사용자 확정 사항) - **이식 방식**: JSP → Next.js 리라이트 (백엔드도 Java→Node 재작성, vexplor_rps `backend-node` 패턴 채택) - **스키마 정책**: **하이브리드** — 도메인 테이블(`contract_mgmt`, `estimate_mgmt`, `sales_registration` 등)은 wace_plm 원본 스키마를 그대로 가져오고, 거래처/품목 등 **마스터는 vexplor_rps 기존 테이블(`customer_mng`, `item_info`)에 매핑** - **관리자 메뉴**: 이식 대상 아님 (vexplor 그대로 사용) - **이식 대상 메뉴 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. 메뉴 매핑표 | # | 메뉴명 | wace_plm URL | wace_plm JSP | wace_plm Controller / Service | vexplor_rps 신규 위치 (제안) | 상세 문서 | |---|---|---|---|---|---|---| | 1 | 견적관리 | `/contractMgmt/estimateList_new.do` | `contractMgmt/estimateList_new.jsp` (47KB) | `ContractMgmtController` (1629–2510 line) / `ContractMgmtService` | `app/(main)/COMPANY_16/sales/estimate/page.tsx` + `backend-node/src/{routes,services}/estimateRoutes.ts` | [01-estimate.md](./01-estimate.md) | | 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/` 신규 경로로 작성하는 것을 권장. ## 2. 도메인 테이블 (wace_plm → vexplor_rps 그대로 이식) 이식 대상 테이블. 새 vexplor_rps DB에 **CREATE TABLE 그대로 적용** (컬럼명/타입 유지). | 우선순위 | 테이블 | 용도 | dbexport.pgsql line | |---|---|---|---| | ★★★ | `contract_mgmt` | 계약/주문서 헤더 | 2488 | | ★★★ | `contract_item` | 계약/주문서 상세 라인 | (DDL: `database/contract_item_tables.sql`) | | ★★★ | `estimate_mgmt` | 견적 헤더 (구버전) | 4340 | | ★★★ | `estimate_template` | 견적 템플릿 = **새 견적의 헤더**(estimateList_new가 사용) | (CREATE 미발견, 운영 DB 추출 필요) | | ★★★ | `estimate_template_item` | 견적 템플릿 라인 = **새 견적의 상세** | (CREATE 미발견, 운영 DB 추출 필요) | | ★★ | `counselingmgmt` | 상담관리 — **이식 대상 아님** (사용자 확정) | 2989 | | ★★ | `contract_mgmt_option` | 주문 옵션 | 2899 | | ★★ | `contract_item_serial` | 주문 라인 시리얼 | (DDL: `database/contract_item_tables.sql`) | | ★★ | `sales_registration` | 판매 등록 (= 판매·매출 집계 원장) | (dbexport에서 위치 확인 필요) | | ★ | `attach_file_info` | 첨부파일 (PDF/이미지) | 1387 | | ★ | `approval` | 결재 | 507 | | ★ | `mail_log` | 메일 발송 로그 (견적 메일링) | (필요 시) | | ★ | `pms_pjt_year_goal` | 연도 목표 (대시보드용) | (필요 시) | ### ✅ 운영 DB DDL 추출 완료 (2026-05-07) 운영 DB(`211.115.91.141:11133/waceplm` PG 16.8)에서 누락된 5개 테이블을 추출하여 [ddl-extracted/](./ddl-extracted/)에 정리. 주요 발견: - `estimate_template` / `estimate_template_item` 발견 → [100_create_estimate_template.sql](./ddl-extracted/100_create_estimate_template.sql) - `sales_registration` / `shipment_log` 발견 → [101_create_sales_registration.sql](./ddl-extracted/101_create_sales_registration.sql) - `mail_log` 발견 → [102_create_mail_log.sql](./ddl-extracted/102_create_mail_log.sql) - `final_data`, `end_count`, `transaction_statement_*`, `tax_invoice_*` — **존재하지 않음** - 매출관리의 마감/세금계산서/수출신고 컬럼은 모두 `shipment_log`에 통합되어 있음 - 운영 데이터 카운트: 견적 5건, 견적라인 7건, 판매 10건, 분할출하 0건, 메일로그 7,805건 → **도메인은 사실상 신규 시스템, 데이터 이주 부담 거의 없음** 자세한 내용은 [ddl-extracted/README.md](./ddl-extracted/README.md). ## 3. 마스터 매핑 (wace_plm 마스터 → vexplor_rps 마스터로 변환) | wace_plm 테이블 | wace_plm 키 컬럼 | vexplor_rps 테이블 | vexplor_rps 키 | 변환 규칙 | |---|---|---|---|---| | `SUPPLY_MNG` (공급/고객사 통합) | `objid` (numeric) | `customer_mng` | `id` (integer) | 양방향 매핑 테이블 필요 (`legacy_supply_objid → customer_mng.id`) | | `CLIENT_MNG` (일반 거래처) | `objid` (numeric) | `customer_mng` | `id` | 위와 동일 (wace_plm은 `customer_objid`가 `'C_'` 접두사로 두 테이블 구분) | | `USER_INFO` | `user_id` | `user_info` | `user_id` | 동일 키 사용 가능 (있다면 그대로) | | `DEPT_INFO` | `dept_code` | (vexplor_rps `department`) | (확인 필요) | TBD | | `PART_MGMT` / `PRODUCT_MGMT` | `objid` | `item_info` | `id` | 매핑 테이블 필요 | | `ATTACH_FILE_INFO` | `target_objid` + `doc_type` | (vexplor_rps 파일 시스템) | (확인 필요) | TBD | ### 마스터 마이그레이션 절차 (제안) 1. wace_plm `SUPPLY_MNG` + `CLIENT_MNG` → `customer_mng`로 INSERT (legacy_objid 컬럼 추가) 2. wace_plm `PART_MGMT` (+ `PRODUCT_MGMT`?) → `item_info`로 INSERT (legacy_part_objid 컬럼 추가) 3. 도메인 테이블(`contract_mgmt`, `estimate_mgmt` 등) 이식 시 외래 컬럼은 wace_plm objid를 그대로 가져오고, 별도 매핑 테이블(`legacy_id_map`)을 통해 vexplor_rps 마스터 id로 변환 ## 4. 백엔드 패턴 (vexplor_rps `backend-node`) ### 라우트 (예: `quoteRoutes.ts` 패턴) ```ts import { Router } from "express"; import { authenticateToken } from "../middleware/authMiddleware"; import * as ctrl from "../controllers/Controller"; const router = Router(); router.use(authenticateToken); router.get("/list", ctrl.getList); router.get("/generate-number", ctrl.generateNumber); router.get("/:id", ctrl.getById); router.post("/", ctrl.create); router.put("/:id", ctrl.update); router.delete("/:id", ctrl.remove); export default router; ``` ### 서비스 (예: `quoteService.ts` 패턴) - `getPool()` (PG raw) - companyCode 멀티테넌시: `WHERE company_code = $1` (RPS는 `COMPANY_16` 고정) - 소프트삭제: `use_yn = 'Y'` - 트랜잭션: `pool.connect()` → `BEGIN/COMMIT/ROLLBACK` - 자동 채번: `generateNumber()` (예: `EST-YYYYMMDD-001`) ### 컨트롤러 - 인증: `req.user.companyCode`, `req.user.userId` 사용 - 응답: `res.json({ data, totalCount })` 또는 `res.json({ success, message })` ## 5. 프론트엔드 패턴 (vexplor_rps Next.js) ```tsx "use client"; import { DataGrid, DataGridColumn } from "@/components/common/DataGrid"; import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter"; import { apiClient } from "@/lib/api/client"; import { useAuth } from "@/hooks/useAuth"; // ... shadcn/ui Button, Dialog, Select, Input, Label ... ``` - 검색 필터: `DynamicSearchFilter` (테이블 카테고리/공통코드 자동 바인딩) - 그리드: `DataGrid` (선택, 정렬, 포맷팅) - 채번 / 단일 조회 / 저장 / 삭제: `apiClient.get/post/put/delete` - 권한: `useCurrent2ndLevelMenuObjid` + 관리자에서 권한 매핑 ## 6. 진행 체크리스트 (메뉴별) 각 메뉴 1개 이식 = 다음 9단계. - [ ] (a) 운영 DB에서 해당 테이블 schema-only dump 추출 - [ ] (b) `db/migrations/` 에 `NNN_create__tables.sql` 작성 - [ ] (c) wace_plm Controller endpoint 목록 추출 → `backend-node/src/routes/Routes.ts` 매핑표 작성 - [ ] (d) `backend-node/src/services/Service.ts` 작성 (raw SQL) - [ ] (e) `backend-node/src/controllers/Controller.ts` 작성 - [ ] (f) `backend-node/src/server.ts` 또는 라우트 등록 위치에 마운트 - [ ] (g) `frontend/app/(main)/COMPANY_16/sales//page.tsx` 작성 (DataGrid + Filter + Modal) - [ ] (h) 메뉴 트리에 등록 (vexplor_rps 메뉴 관리 화면 또는 메뉴 시드) - [ ] (i) 권한 매핑 + 수동 테스트 ## 7. 다음 작업 1. ~~운영 DB DDL 추출~~ 완료 (2026-05-07) 2. ~~01~04 상세 매핑 + 1차 이식~~ 완료 (2026-05-08) 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개 메뉴 동일 적용) ### 8.1 버튼 영역 상단 우측 버튼 영역에 다음 순서로 배치 — **모든 메뉴 공통**: 1. `조회` (`variant="outline"` + `` 아이콘) 2. 메뉴 고유 액션 버튼들 (등록/수정/삭제/확정 등) 3. **`초기화`** (`variant="ghost"`) — `searchForm` state를 모든 키 빈 값으로 reset ```tsx ``` ### 8.2 date input - `` 사용 시 별도 처리 불필요. 다음은 `Input` 컴포넌트 + `globals.css`에 자동 반영: - 빈 값일 때 `'YYYY/MM/DD'` 자리표시 텍스트 숨김 (data-empty="true" 자동) - 캘린더 아이콘 숨김 (`::-webkit-calendar-picker-indicator { display: none }`) - input 영역 어디 클릭해도 picker 자동 표시 (`showPicker()`) - 위 동작은 `frontend/components/ui/input.tsx` + `frontend/app/globals.css`에서 일괄 처리. ### 8.3 검색 폼 그리드 - `grid grid-cols-2 md:grid-cols-4 lg:grid-cols-7 gap-x-2 gap-y-1.5 p-2 border rounded-md bg-muted/30` - 라벨: `text-[11px] mb-0.5 block text-muted-foreground` - 입력: `h-8 text-xs` (date는 `px-1 flex-1 min-w-0` 추가) ### 8.4 wace JSP 주석 함정 검색 폼 추출 시 JSP 끝부분 `` 블록은 **이식 대상 아님**. 활성/비활성 분리 표로 문서화. 자세한 내용은 메모리 [feedback_wace_jsp_columns](../../../../.claude/projects/-Users-jhj-vexplor-rps/memory/feedback_wace_jsp_columns.md).