# 02. 주문서관리 이식 상세

> 원본: `/contractMgmt/orderMgmtList.do` (orderMgmtList.jsp, 45KB)
> 대상: `app/(main)/COMPANY_16/sales/order/page.tsx` (기존 골격 대체 또는 신규 경로)

## 1. 화면 구조 (wace_plm 원본)

### 1.1 검색 폼 (`#plmSearchZon`)

**활성 9개** (orderMgmtList.jsp line 1109~1182):

| 필드 | name | 타입 | 비고 |
|---|---|---|---|
| 주문유형 | `category_cd` | select2 (공통코드) | |
| 발주번호 | `search_poNo` | text | |
| 고객사 | `customer_objid` | select2 (`SUPPLY_MNG`+`CLIENT_MNG`) | `'C_'` 접두사 |
| 품번 | `search_partNo` | select2-part | hidden `search_partObjId` 동행 |
| 품명 | `search_partName` | select2-part | |
| 시리얼 | `search_serialNo` | text | |
| 수주상태 | `contract_result` | select2 (공통코드) | |
| 발주일 from~to | `order_start_date` / `order_end_date` | date_icon | |
| 요청납기 from~to | `due_start_date` / `due_end_date` | date_icon | |

**비활성 (JSP line 1184~ `<!-- 주석처리된 검색필터 -->` 블록 안)** — 이식 대상 아님:

| 필드 | name |
|---|---|
| 제품 | `product` |
| 지역 | `area_cd` |
| 유/무상 | `paid_type` |
| 환종 | `contract_currency` |

### 1.2 버튼 영역

| 버튼 | id | 동작 |
|---|---|---|
| 조회 | `btnSearch` | `fn_search()` → `/contractMgmt/contractGridList.do` |
| 수주복사 | `btnCopy` | `/contractMgmt/copyEstimateAndOrderInfo.do` |
| 수주입력 | `.btnRegist` | 팝업: `/contractMgmt/estimateAndOrderRegistFormPopup.do` 또는 `orderRegistFormPopup.do` |
| 수주확정 | `btnOrderConfirm` | (녹색) — 견적 → 수주 확정 처리 |
| 수주취소 | `btnOrderCancel` | (빨강) — `/contractMgmt/saveOrderCancelQty.do` (수량별 취소) |
| 결재상신 | `btnApproval` | 결재 워크플로우 |

### 1.3 그리드 (`_tabulGrid` = Tabulator)

데이터 소스: `POST /contractMgmt/contractGridList.do`

| # | 컬럼 | field | 정렬 | 비고 |
|---|---|---|---|---|
| 1 | 영업번호 | CONTRACT_NO | center | frozen, anchor → `fn_projectConceptDetail(OBJID)` |
| 2 | 주문유형 | CATEGORY_NAME | center | |
| 3 | 발주일 | ORDER_DATE | center | |
| 4 | 발주번호 | PO_NO | center | |
| 5 | 요청납기 | EARLIEST_DUE_DATE | center | "{date} 외 {n}건" 포맷 |
| 6 | 고객사 | CUSTOMER_NAME | left | |
| 7 | 품명 | ITEM_SUMMARY | left | "{itemName} 외 {n}건" |
| 8 | 수주수량 | ORDER_QUANTITY | right | |
| 9 | 수주취소 | CANCEL_QTY_SUM | right | |
| 10 | 유/무상 | PAID_TYPE | center | |
| 11 | 수주상태 | CONTRACT_RESULT_NAME | left | |
| 12 | 공급가액 | ORDER_SUPPLY_PRICE_SUM | right | 통화기호 |
| 13 | 부가세 | ORDER_VAT_SUM | right | |
| 14 | 총액 | ORDER_TOTAL_AMOUNT_SUM | right | |
| 15 | 원화총액 | ORDER_TOTAL_AMOUNT_KRW | right | |
| 16 | 주문서첨부 | CU01_CNT | center | 📎 클릭 → `/contractMgmt/FileRegistPopup.do` |
| 17 | 주문서 | HAS_ORDER_DATA | center | 클릭 → `/contractMgmt/orderFormView.do?objId=...` |
| 18 | 고객사요청사항 | CUSTOMER_REQUEST | left | |
| 19 | 결재상태 | ORDER_APPR_STATUS | center | |
| 20 | 환종 | CONTRACT_CURRENCY_NAME | center | |
| 21 | 환율 | EXCHANGE_RATE | right | |
| 22 | S/N | SERIAL_NO | center | |
| 23 | 품번 | PART_NO | center | |
| 24 | 작성자 | WRITER_NAME | center | |
| 25 | 제품구분 | PRODUCT_NAME | center | |
| 26 | 국내/해외 | AREA_NAME | center | |
| 27 | 접수일 | RECEIPT_DATE | center | |

> 일부 셀 클릭은 `contractList.jsp`로 form submit되어 다른 화면 이동 (line 194 참고).

## 2. 백엔드 endpoint 매핑

| wace_plm endpoint | 메서드 | 용도 | vexplor_rps 신규 endpoint |
|---|---|---|---|
| `/contractMgmt/orderMgmtList.do` | GET | 페이지 진입 | (Next.js page) |
| `/contractMgmt/contractGridList.do` | POST | 그리드 데이터 | `GET /api/sales/order/list?...` |
| `/contractMgmt/getPagingContractList.do` | POST | 페이징 조회 (legacy) | 위와 통합 |
| `/contractMgmt/orderRegistFormPopup.do` | GET | 수주 등록/수정 팝업 | (Next.js Dialog) |
| `/contractMgmt/estimateAndOrderRegistFormPopup.do` | GET | 견적+수주 통합 팝업 | (Next.js Dialog with mode) |
| `/contractMgmt/saveOrderInfo.do` | POST | 수주 저장 | `POST /api/sales/order` / `PUT /api/sales/order/:id` |
| `/contractMgmt/saveEstimateAndOrderInfo.do` | POST | 견적+수주 통합 저장 | 위와 통합 (mode 파라미터) |
| `/contractMgmt/copyEstimateAndOrderInfo.do` | POST | 수주 복사 | `POST /api/sales/order/:id/copy` |
| `/contractMgmt/updateOrderStatus.do` | POST | 수주 상태 변경(확정/취소 등) | `PATCH /api/sales/order/:id/status` |
| `/contractMgmt/saveOrderCancelQty.do` | POST | 라인별 취소 수량 저장 | `POST /api/sales/order/:id/cancel-qty` |
| `/contractMgmt/getContractItems.do` | POST | 수주 라인 조회 | `GET /api/sales/order/:id/items` |
| `/contractMgmt/getAllSerialNumbers.do` | POST | 라인 전체 시리얼 조회 | `GET /api/sales/order/:id/serials` |
| `/contractMgmt/orderFormView.do` | GET | 주문서 양식 화면 | `app/(pop)/sales/order/[id]/form/page.tsx` |
| `/contractMgmt/getOrderFormData.do` | POST | 주문서 양식 데이터 | `GET /api/sales/order/:id/form-data` |
| `/contractMgmt/getOrderTotalAmount.do` | POST | 합계 계산 | `GET /api/sales/order/:id/totals` |
| `/contractMgmt/orderMgmtGrodList.do` (typo) | POST | (구버전 그리드) | (사용 안함) |
| `/contractMgmt/getContractItemList.do` | POST | 계약 라인 조회 | 위 `/items`와 통합 |
| `/contractMgmt/contracMgmtReviewFormPopup.do` (typo) | GET | 계약 리뷰 팝업 | (Next.js Dialog) |
| `/contractMgmt/saveContractMgmtReviewInfo.do` | POST | 리뷰 정보 저장 | `POST /api/sales/order/:id/review` |
| `/contractMgmt/overlapOrder.do` | POST | 중복 수주 체크 | `GET /api/sales/order/check-duplicate?...` |
| `/contractMgmt/checkApprovalRequired.do` | POST | 결재 필요 여부 | (견적과 공유) |
| `/contractMgmt/deleteContractMngInfo.do` | POST | 계약(수주) 삭제 | `DELETE /api/sales/order/:id` |
| `/contractMgmt/FileRegistPopup.do` | GET | 첨부파일 팝업 | (공유 컴포넌트) |
| `/contractMgmt/getCustomerManagerList.do` | POST | 고객사 담당자 목록 | `GET /api/customers/:id/contacts` |
| `/contractMgmt/checkProjectExists.do` | POST | 프로젝트 존재 체크 | (견적과 공유) |

> `OrdersMgmtController` (다른 컨트롤러, `/ordersMgmt/*`)는 별개 모듈로 보임 (입출고/미납 관리 위주). 본 메뉴와는 무관.

## 3. DB 테이블 (이식 대상)

| 테이블 | 역할 | DDL 출처 |
|---|---|---|
| `contract_mgmt` | 수주(계약) 헤더 — **주문서의 메인 테이블** | `db/dbexport.pgsql:2488` |
| `contract_item` | 수주 라인 (품번/수량/단가/공급가/부가세/총액 + ORDER_* 컬럼들) | `database/contract_item_tables.sql` + `database/add_order_columns_to_contract_item.sql` |
| `contract_item_serial` | 라인별 S/N | `database/contract_item_tables.sql` |
| `contract_mgmt_option` | 수주 옵션 | `db/dbexport.pgsql:2899` |
| `contract_base_data` | 수주 기준 데이터 (공통 마스터) | (운영 DB 추출 필요) |
| `attach_file_info` | 주문서 첨부 (`doc_type='contractMgmt01'`) | `db/dbexport.pgsql:1387` |
| `approval` | 결재 | `db/dbexport.pgsql:507` |

### 3.1 contract_item 핵심 컬럼 (DDL 합본)

```sql
CREATE TABLE CONTRACT_ITEM (
    OBJID               VARCHAR(50)     PRIMARY KEY,
    CONTRACT_OBJID      VARCHAR(50)     NOT NULL REFERENCES CONTRACT_MGMT(OBJID) ON DELETE CASCADE,
    SEQ                 INTEGER         NOT NULL,
    PART_NO             VARCHAR(100)    NOT NULL,           -- → vexplor_rps item_info 매핑
    PART_NAME           VARCHAR(200)    NOT NULL,
    QUANTITY            INTEGER         NOT NULL DEFAULT 1, -- 견적 수량
    DUE_DATE            VARCHAR(10),
    CUSTOMER_REQUEST    TEXT,
    -- 수주 컬럼 (add_order_columns 추가)
    ORDER_QUANTITY      VARCHAR(50),
    ORDER_UNIT_PRICE    VARCHAR(50),
    ORDER_SUPPLY_PRICE  VARCHAR(50),
    ORDER_VAT           VARCHAR(50),
    ORDER_TOTAL_AMOUNT  VARCHAR(50),
    -- 공통
    REGDATE             TIMESTAMP       NOT NULL DEFAULT NOW(),
    WRITER              VARCHAR(50),
    CHGDATE             TIMESTAMP,
    CHG_USER_ID         VARCHAR(50),
    STATUS              VARCHAR(20)     DEFAULT 'ACTIVE'
);

CREATE TABLE CONTRACT_ITEM_SERIAL (
    OBJID               VARCHAR(50)     PRIMARY KEY,
    ITEM_OBJID          VARCHAR(50)     NOT NULL REFERENCES CONTRACT_ITEM(OBJID) ON DELETE CASCADE,
    SEQ                 INTEGER         NOT NULL,
    SERIAL_NO           VARCHAR(200)    NOT NULL,
    REGDATE             TIMESTAMP       NOT NULL DEFAULT NOW(),
    WRITER              VARCHAR(50),
    STATUS              VARCHAR(20)     DEFAULT 'ACTIVE',
    UNIQUE (ITEM_OBJID, SERIAL_NO)
);
```

### 3.2 contract_mgmt 주요 컬럼 (참고)

`db/dbexport.pgsql:2488-2540` 참조. 주문서에서 자주 쓰는 컬럼:
- `objid` (PK), `contract_no` (영업번호 = CONTRACT_NO)
- `customer_objid` (고객사 — `'C_'` prefix로 client_mng 분기)
- `category_cd`, `area_cd`, `product`, `paid_type` (공통코드)
- `po_no` (발주번호), `contract_date` (발주일), `req_del_date` (요청납기)
- `contract_currency`, `contract_currency_name`, `exchange_rate`(?)
- `result_cd` → `contract_result_name` (수주상태)
- `pm_user_id`, `salesman` (담당자)
- `regdate`, `writer`, `chg_user_id`

## 4. 구현 순서

1. **마이그레이션 SQL 작성**: `db/migrations/101_create_order_tables.sql` (contract_mgmt + contract_item + contract_item_serial + contract_mgmt_option + contract_base_data)
2. **백엔드**:
   - `backend-node/src/routes/orderRoutes.ts`
   - `backend-node/src/controllers/orderController.ts`
   - `backend-node/src/services/orderService.ts` (트랜잭션 필수: 헤더 + 라인 + 시리얼 동시 저장)
3. **프론트엔드**:
   - `frontend/app/(main)/COMPANY_16/sales/order/page.tsx` (기존 파일 백업 후 신규 작성, 또는 새 라우트 `sales/contract-order`)
   - 수주 등록/수정 Dialog (`components/sales/OrderRegistDialog.tsx`)
   - 수주확정/취소 모달 (수량 입력)
   - 라인별 시리얼 관리 모달
   - 주문서 양식 출력 페이지 (PDF/인쇄)

## 5. 주의사항

- **견적 → 수주 전환**: `estimateAndOrderRegistFormPopup`은 견적 OBJID를 받아 수주 헤더를 생성하면서 라인을 복사. 트랜잭션 + 라인 컬럼 매핑(견적 QUANTITY → ORDER_QUANTITY) 필요.
- **라인별 부분 취소**: `saveOrderCancelQty`는 라인의 ORDER_QUANTITY 일부만 취소하는 시나리오. UI에서 취소 수량 입력 → DB는 별도 취소이력 테이블 사용 가능성 (확인 필요).
- **주문서 양식 (orderFormView)**: 인쇄용 별도 화면. PDF 다운로드 또는 인쇄 다이얼로그.
- **계약 리뷰 (Review)**: 사내 검토 단계 — 별도 결재 또는 메모/체크리스트. `contracMgmtReviewFormPopup` (typo) 분석 필요.
- **vexplor_rps 기존 `sales/order/page.tsx`** ([order/page.tsx](../../../frontend/app/(main)/COMPANY_16/sales/order/page.tsx))는 이 도메인이 아닐 가능성. 백업 후 신규 작성 권장.
