Files
wace_rps/docs/migration/sales/README.md
T
hjjeong 3db55d9fd9 구매요청 2메뉴 경로 정렬 + UI 문자열 정리 + 이식 문서 추가
페이지 경로 이동 (menu_info 등록 경로와 일치)
- sales/purchase-request → purchase-request/request (구매요청서관리)
- sales/purchase-proposal → purchase-request/proposal (품의서관리)
- 사이드바 '구매요청' top-level 그룹(objid=100025) 하위 2개 메뉴와 1:1 매칭

UI 문자열 정리
- PageHeader description 2곳에서 wace 매퍼명 노출 제거
- 미구현 액션 toast 3개를 일반 안내문으로 교체 ("XXX 기능은 준비 중입니다.")

문서
- docs/migration/sales/09-purchase-request.md 신규 — 두 메뉴 매핑/컬럼/SQL 정합성/구매관리>품의서관리와의 차이/백로그
- docs/migration/sales/README.md — 이식 대상 4개 → 6개, 매핑표/다음작업 갱신

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 12:24:00 +09:00

188 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 영업관리 이식 (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` (16292510 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` (25043169 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/<feature>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_<feature>_tables.sql` 작성
- [ ] (c) wace_plm Controller endpoint 목록 추출 → `backend-node/src/routes/<feature>Routes.ts` 매핑표 작성
- [ ] (d) `backend-node/src/services/<feature>Service.ts` 작성 (raw SQL)
- [ ] (e) `backend-node/src/controllers/<feature>Controller.ts` 작성
- [ ] (f) `backend-node/src/server.ts` 또는 라우트 등록 위치에 마운트
- [ ] (g) `frontend/app/(main)/COMPANY_16/sales/<feature>/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"` + `<Search>` 아이콘)
2. 메뉴 고유 액션 버튼들 (등록/수정/삭제/확정 등)
3. **`초기화`** (`variant="ghost"`) — `searchForm` state를 모든 키 빈 값으로 reset
```tsx
<Button size="sm" variant="ghost"
onClick={() => setSearchForm({ /* 모든 키 "" */ })}>
</Button>
```
### 8.2 date input
- `<Input type="date">` 사용 시 별도 처리 불필요. 다음은 `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).