# 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**: 한국어 양식, 워터마크/도장 처리 필요할 수 있음.
