# 03. 판매관리 이식 상세 > 원본: `/contractMgmt/salesMgmtList.do` (위임) → `/salesNcollectMgmt/sales.do` 또는 `/salesMgmt/salesMgmt` > JSP: `salesmgmt/salesMgmt/salesMgmtList.jsp` (34KB) > Controller: `SalesNcollectMgmtController` (line 763~) > 대상: `app/(main)/COMPANY_16/sales/sale/page.tsx` (신규) ## 1. 화면 구조 (wace_plm 원본) 수주된 항목을 출하지시 → 판매등록 → 거래명세서 발행까지 처리. ### 1.1 검색 폼 | 필드 | name | 타입 | 비고 | |---|---|---|---| | 주문유형 | `orderType` | select2 (공통코드) | | | 발주번호 | `poNo` | text | | | 고객사 | `customer_objid` | select2 | | | 품번 | `search_partNo` | select2-part | hidden `search_partObjId` | | 품명 | `search_partName` | select2-part | | | 시리얼 | `serialNo` | text | | | 출하상태 | `shippingStatus` | select2 | | | 발주일 from~to | `orderDateFrom` / `orderDateTo` | date_icon | | | 출하일 from~to | `shippingDateFrom` / `shippingDateTo` | date_icon | | | 판매상태 | `salesStatus` | select2 multiple | 다중 선택 | ### 1.2 버튼 | 버튼 | id | 동작 | |---|---|---| | 조회 | `btnSearch` | `/salesMgmt/salesMgmtGridList.do` | | 출하지시/판매등록 | `btnBulkRegister` | (녹색) → `/salesMgmt/salesRegForm.do` 새창 | | ~~거래명세서 생성~~ | (주석) | 매출관리에서 사용 | | ~~분할출하~~ | (주석) | `/salesMgmt/splitShipmentForm.do` | ### 1.3 그리드 컬럼 데이터 소스: `POST /salesMgmt/salesMgmtGridList.do` | # | 컬럼 | field | 정렬 | |---|---|---|---| | 1 | 프로젝트번호 | PROJECT_NO | center, frozen | | 2 | 주문유형 | ORDER_TYPE | center | | 3 | 발주일 | ORDER_DATE | center | | 4 | 발주번호 | PO_NO | center | | 5 | 요청납기 | REQUEST_DATE | center | | 6 | 출하일 | SHIPPING_DATE_WITH_COUNT | center | | 7 | 고객사 | CUSTOMER | left | | 8 | 품명 | PRODUCT_NAME | left | | 9 | 수주수량 | ORDER_QUANTITY | right | | 10 | 판매수량 | SALES_QUANTITY | right | | 11 | 잔량 | REMAINING_QUANTITY | right | | 12 | 판매단가 | SALES_UNIT_PRICE | right | | 13 | 판매공급가액 | SALES_SUPPLY_PRICE | right | | 14 | 부가세 | SALES_VAT | right | | 15 | 판매총액 | SALES_TOTAL_AMOUNT | right | | 16 | 판매원화총액 | SALES_TOTAL_AMOUNT_KRW | right | | 17 | 잔량원화총액 | REMAINING_AMOUNT_KRW | right | | 18 | 수주상태 | ORDER_STATUS | center | | 19 | 판매상태 | SALES_STATUS | center | | 20 | 생산상태 | PRODUCTION_STATUS | center | | 21 | 출하지시상태 | SHIPPING_ORDER_STATUS | center | | 22 | 유/무상 | PAYMENT_TYPE | center | | 23 | 환종 | SALES_CURRENCY_NAME | center | | 24 | 환율 | SALES_EXCHANGE_RATE | right | | 25 | S/N | SERIAL_NO | left | | 26 | 분할S/N | SPLIT_SERIAL_NO | left | | 27 | 품번 | PRODUCT_NO | left | | 28 | 제품구분 | PRODUCT_TYPE | center | | 29 | 국내/해외 | NATION | center | | 30 | 접수일 | RECEIPT_DATE | center | | 31 | 고객사요청사항 | CUSTOMER_REQUEST | left | | 32 | 주문서첨부 | CU01_CNT | center | | 33 | 출하방법 | SHIPPING_METHOD | center | | 34 | 담당자 | MANAGER | center | | 35 | 인도조건 | INCOTERMS | center | | 36 | 거래명세서 | TRANSACTION_STATEMENT | center | ## 2. 백엔드 endpoint 매핑 | wace_plm endpoint | 메서드 | 용도 | vexplor_rps 신규 endpoint | |---|---|---|---| | `/contractMgmt/salesMgmtList.do` | GET | (위임) → `/salesmgmt/salesMgmt/salesMgmtList` 렌더 | (Next.js page) | | `/salesNcollectMgmt/sales.do` | GET | 판매 화면 직접 진입 | (Next.js page) | | `/salesMgmt/salesMgmtGridList.do` | POST | 그리드 데이터 | `GET /api/sales/sale/list?...` | | `/salesMgmt/salesRegForm.do` | GET | 출하지시/판매등록 폼 | `app/(pop)/sales/sale/register/page.tsx` (43KB JSP) | | `/salesMgmt/saveSales.do` | POST | 판매 저장 | `POST /api/sales/sale` | | `/salesMgmt/splitShipmentForm.do` | GET | 분할출하 폼 | `app/(pop)/sales/sale/split/page.tsx` (23KB JSP) | | `/salesMgmt/saveSplitShipment.do` | POST | 분할출하 저장 | `POST /api/sales/sale/split` | | `/salesMgmt/shippingDetailPopup.do` | GET | 출하 상세 팝업 | (Next.js Dialog) | | `/salesMgmt/transactionStatementForm.do` | GET | 거래명세서 폼 | `app/(pop)/sales/sale/transaction/page.tsx` (31KB JSP) | | `/salesMgmt/getTransactionStatementData.do` | POST | 거래명세서 데이터 | `GET /api/sales/sale/transaction-statement?...` | | `/salesMgmt/saveTransactionStatement.do` | POST | 거래명세서 저장 | `POST /api/sales/sale/transaction-statement` | | `/salesMgmt/getSavedTransactionStatement.do` | POST | 저장된 거래명세서 조회 | `GET /api/sales/sale/transaction-statement/:id` | | `/salesMgmt/getAllSerialNumbers.do` | POST | 시리얼 번호 일괄 조회 | (주문과 공유 가능) | | `/salesNcollectMgmt/contractList.do` | GET | (대시보드 화면) | TBD | | `/salesNcollectMgmt/salesMgmtFormPopup.do` | GET | 판매 폼 팝업 (구버전) | (사용 안할 듯) | | `/salesNcollectMgmt/saveSalesMgmt.do` | POST | 판매 저장 (구버전) | 위와 통합 | | `/salesNcollectMgmt/deleteSalesMgmt.do` | POST | 판매 삭제 | `DELETE /api/sales/sale/:id` | | `/salesNcollectMgmt/salesDeadlineConfirm.do` | POST | 매출 마감 확인 | (매출관리에서 호출) | ## 3. DB 테이블 > **2026-05-07 운영 DB 직접 확인 결과**: `transaction_statement_*` 테이블은 존재하지 않음. 거래명세서는 별도 테이블 없이 화면 폼에서 출력만 하거나, attach_file_info에 PDF로 저장하는 방식으로 보임 (정확한 위치는 `getSavedTransactionStatement.do` SQL 분석 필요). | 테이블 | 역할 | DDL | |---|---|---| | `sales_registration` | 판매 등록 헤더 (프로젝트당 1건, UNIQUE `project_no`) — **메인** | [ddl-extracted/101_create_sales_registration.sql](./ddl-extracted/101_create_sales_registration.sql) | | `shipment_log` | 분할출하 이력 라인 (FK `parent_sale_no` → sales_registration) | 위 | | `contract_mgmt` | 수주 헤더 (조인 필수, target_objid로 연결) | `db/dbexport.pgsql:2488` | | `contract_item` | 수주 라인 (조인 필수) | `database/contract_item_tables.sql` | | `contract_item_serial` | 시리얼 (분할출하 시 분리) | 위 | | `production_result` | 생산 결과 (조인 — `PRODUCTION_STATUS`) | (참고만) | ### 핵심 관계 ``` contract_mgmt (수주 헤더) └─ contract_item (수주 라인) └─ contract_item_serial (라인별 S/N) contract_mgmt.objid = sales_registration.project_no? (정확한 매칭 키 확인 필요) = shipment_log.target_objid sales_registration (판매 헤더, 프로젝트당 1건) ↑ parent_sale_no └─ shipment_log (분할출하 + 매출마감 이력 N건) ``` > ⚠️ `sales_registration.project_no`와 `contract_mgmt.objid`(또는 `contract_no`)의 매칭 키는 운영 SQL에서 재확인 필요. 칼럼명은 `project_no`이지만 실제로는 contract_mgmt를 가리킬 가능성이 큼. ## 4. 구현 순서 1. **운영 DB DDL 추출** (sales_registration, shipment_log, transaction_statement) 2. **마이그레이션 SQL**: `db/migrations/102_create_sale_tables.sql` 3. **백엔드**: - `backend-node/src/routes/saleRoutes.ts` - `backend-node/src/services/saleService.ts` (그리드 SQL은 contract_mgmt + contract_item + sales_registration JOIN, 잔량 계산 = ORDER_QUANTITY - SUM(SALES_QUANTITY)) - 분할출하 트랜잭션 (라인 분리 + 시리얼 재할당) 4. **프론트엔드**: - `frontend/app/(main)/COMPANY_16/sales/sale/page.tsx` (목록) - `app/(pop)/sales/sale/register/page.tsx` (출하지시/판매등록 폼) - `app/(pop)/sales/sale/split/page.tsx` (분할출하) - `app/(pop)/sales/sale/transaction/page.tsx` (거래명세서) ## 5. 주의사항 - **잔량(REMAINING_QUANTITY) 계산**: `ORDER_QUANTITY - SUM(sales_registration.SALES_QUANTITY where contract_item_objid = ?)`. 그리드는 라인 단위 또는 헤더 단위 표시 모드가 있을 수 있음 — 원본 SQL 확인 필요. - **분할출하**: 한 라인의 시리얼을 여러 출하건으로 나누는 시나리오. UI 복잡도 높음. - **거래명세서**: 여러 판매건을 묶어 1장으로 발행. `projectNos` 파라미터로 다건 처리. - **상태 컬럼들**: ORDER_STATUS / SALES_STATUS / PRODUCTION_STATUS / SHIPPING_ORDER_STATUS 4개를 동시 표시 — 각 상태 머신 정의 필요. - **환율 컬럼 분리**: 수주(`exchange_rate`)와 판매(`sales_exchange_rate`)를 별도 보유 — 시점 환율을 잠그는 정책. - **거래명세서 PDF**: 한국어 양식, 워터마크/도장 처리 필요할 수 있음.