# 영업관리 > 구매요청서관리 · 품의서관리 (2026-05-15 신규 2메뉴)

> 작성: 2026-05-15 / 작성자: hjjeong
> 원본: wace_plm `/salesMng/purchaseRequestRegList.do` + `/salesMng/purchaseRegProposalMngList.do`
> 신규 위치: `frontend/app/(main)/COMPANY_16/sales/{purchase-request,purchase-proposal}/page.tsx`
> 백엔드: `backend-node/src/{services/salesPurchaseRequestService.ts,routes/salesPurchaseRequestRoutes.ts}`

## 0. 한 문장 요약

`sales_request_master` 한 테이블을 `doc_type` 으로 갈라 **구매요청서(`PURCHASE_REG`)** → **품의서(`PURCHASE_REG_PROPOSAL`)** → 결재완료 시 **구매관리>품의서관리**로 흘러가는 3단 파이프라인의 앞단 2단을 영업관리 메뉴에 신설.

## 1. 메뉴 매핑

| # | 메뉴명 | RPS URL | wace URL | wace JSP | wace 매퍼 | DOC_TYPE 필터 |
|---|---|---|---|---|---|---|
| 5 | 구매요청서관리 | `/purchase-request/request` | `/salesMng/purchaseRequestRegList.do` | `salesMng/purchaseRequestRegList.jsp` (728줄) | `salesMng.getSalesRequestMasterGridList` (DOC_TYPE_FILTER='PURCHASE_REG') | `PURCHASE_REG` |
| 6 | 품의서관리 (영업) | `/purchase-request/proposal` | `/salesMng/purchaseRegProposalMngList.do` | `salesMng/purchaseRegProposalMngList.jsp` (313줄) | `salesMng.getPurchaseRegProposalMngGridList` | `PURCHASE_REG_PROPOSAL` |

## 2. 구매관리 > 품의서관리와의 차이 (중요)

| 구분 | 구매관리 > 품의서관리 (`/purchase/proposal`) | 영업관리 > 품의서관리 (`/purchase-request/proposal`) |
|---|---|---|
| wace URL | `/salesMng/proposalMngList.do` | `/salesMng/purchaseRegProposalMngList.do` |
| wace 매퍼 | `getProposalMngGridList` | `getPurchaseRegProposalMngGridList` |
| DOC_TYPE | `PROPOSAL` 전부 + `PURCHASE_REG_PROPOSAL`(결재완료만) | `PURCHASE_REG_PROPOSAL` 전용 |
| 화면 의미 | **발주서 생성 풀** — 결재완료된 품의서 모음 | **결재상신 화면** — 구매요청서에서 만든 품의서 |
| 컬럼 차이 | + 발주서 No, + 공급업체, + 총액 | 헤더 9개 (품의서No/프로젝트/구매유형/주문유형/제품구분/품번/품명/결재상태/작성일/작성자) |
| 액션 | 결재상신 / **발주서생성** | **결재상신** (Amaranth10 SSO) |
| 데이터 흐름 | 영업관리>품의서 결재완료 → 자동 노출 | 구매요청서에서 품의서생성 시 신규 row |

**핵심**: 같은 `sales_request_master` 테이블이지만 `doc_type` 으로 책임이 갈림. 영업관리 측은 작성·결재상신을 담당, 구매관리 측은 결재완료된 것들을 발주서로 변환.

## 3. 3단 파이프라인 흐름

```
[구매요청서관리]              [영업>품의서관리]                 [구매>품의서관리]
PURCHASE_REG                  PURCHASE_REG_PROPOSAL             PURCHASE_REG_PROPOSAL
sales_request_master  ──┬───  sales_request_master      ──┬───  (결재완료만 노출)
PROJECT_NO=프로젝트     │     PROJECT_NO=부모 PURCHASE_REG │     +
+ 작성중/품의서생성/확정 │     OBJID                         │     PROPOSAL 전부
                       │                                  │
                  품의서생성                          결재상신                 발주서생성
              (선택 1건 → POST)                    (Amaranth SSO)              (purchase_order_*)
```

부모-자식 연결: `PURCHASE_REG_PROPOSAL.PROJECT_NO = PURCHASE_REG.OBJID::VARCHAR` (wace 매퍼 1138줄 CASE 절 1:1).

## 4. 그리드 컬럼 매핑

### 4.1 구매요청서관리 (14컬럼)

| # | 라벨 | 필드 | 출처 |
|---|---|---|---|
| 1 | 요청번호 | `request_mng_no` | SRM.REQUEST_MNG_NO |
| 2 | 구매유형 | `purchase_type_name` | comm_code(0001814) |
| 3 | 프로젝트번호 | `project_number` | project_mgmt.PROJECT_NO |
| 4 | 주문유형 | `order_type_name` | project_mgmt.CATEGORY_CD 우선, fallback SRM.ORDER_TYPE |
| 5 | 제품구분 | `product_name_full` | contract_mgmt.PRODUCT 우선, fallback SRM.PRODUCT_NAME |
| 6 | 고객사 | `customer_name` | contract_mgmt.CUSTOMER_OBJID → CLIENT_MNG/SUPPLY_MNG 분기 |
| 7 | 유/무상 | `paid_type_name` | contract_mgmt.PAID_TYPE 우선, fallback SRM.PAID_TYPE |
| 8 | 품번 | `part_display` | MBOM_DETAIL → PART_MNG.PART_NO (+ "외 N건") |
| 9 | 품명 | `part_name_display` | MBOM_DETAIL → PART_MNG.PART_NAME (+ "외 N건") |
| 10 | 구매요청서 | `has_purchase_request_label` | MBOM_HEADER_OBJID 존재 시 "작성" else "미작성" |
| 11 | 작성자 | `request_user_name` | user_name(REQUEST_USER_ID ?? WRITER) |
| 12 | 입고요청일 | `delivery_request_date` | SRM.DELIVERY_REQUEST_DATE |
| 13 | 작성일 | `regdate_title` | TO_CHAR(SRM.REGDATE, 'YYYY-MM-DD') |
| 14 | 상태 | `status_title` | 품의서생성 / 확정 / 작성중 CASE |

### 4.2 품의서관리 (영업) (10컬럼)

| # | 라벨 | 필드 | 출처 |
|---|---|---|---|
| 1 | 품의서 No | `proposal_no` | SRM.REQUEST_MNG_NO |
| 2 | 프로젝트번호 | `project_number` | project_mgmt.PROJECT_NO |
| 3 | 구매유형 | `purchase_type_name` | comm_code(0001814) |
| 4 | 주문유형 | `order_type_name` | 위와 동일 |
| 5 | 제품구분 | `product_name_title` | comm_code(SRM.PRODUCT_NAME) |
| 6 | 품번 | `part_display` | MBOM_DETAIL → PART_MNG |
| 7 | 품명 | `part_name_display` | 위와 동일 |
| 8 | 결재상태 | `status_title` | Amaranth status 우선순위 (결재완료/결재 상신중/반려/등록중) |
| 9 | 작성일 | `regdate_title` | TO_CHAR(SRM.REGDATE) |
| 10 | 작성자 | `writer_name` | user_name(SRM.WRITER) |

## 5. 백엔드 SQL 정합성 메모

### 5.1 상태 CASE (구매요청서)
```sql
CASE
  WHEN EXISTS (
    SELECT 1 FROM SALES_REQUEST_MASTER P
     WHERE P.DOC_TYPE = 'PURCHASE_REG_PROPOSAL'
       AND P.PROJECT_NO = SRM.OBJID::VARCHAR
  ) THEN '품의서생성'
  WHEN SRM.STATUS = 'confirmed' THEN '확정'
  WHEN SRM.STATUS = 'create'    THEN '작성중'
  ELSE COALESCE(SRM.STATUS, '')
END
```
wace 매퍼 `salesMng.xml:1135~1141` 1:1.

### 5.2 결재상태 CASE (품의서)
```sql
CASE
  WHEN AMR.STATUS = 'complete'  THEN 'approvalComplete'
  WHEN AMR.STATUS = 'inProcess' THEN 'inProcess'
  WHEN AMR.STATUS = 'reject'    THEN 'reject'
  ELSE SRM.STATUS
END
```
JOIN: `amaranth_approval AMR ON SRM.OBJID::VARCHAR=AMR.TARGET_OBJID AND AMR.TARGET_TYPE='PROPOSAL'`.
wace 매퍼 `salesMng.xml:4805~4812` 1:1.

### 5.3 sales_request_part 누락 처리
wace 원본은 `SALES_REQUEST_PART` (구매요청 라인) 테이블을 사용해 품번/품명 집계. RPS 에는 미존재 → **MBOM_DETAIL → PART_MNG fallback** (구매관리 패턴 동일). 운영DB DDL 추출 후 전환 예정. 메모리 [feedback_missing_tables_workflow](../../../../.claude/projects/-Users-jhj-vexplor-rps/memory/feedback_missing_tables_workflow.md).

## 6. 액션 구현 (2026-05-15 완료)

| 항목 | 상태 | 구현 위치 |
|---|---|---|
| sales_request_part 신설 | ✅ | `docs/migration/sales/ddl-extracted/106_create_sales_request_part.sql` (운영 DDL 1:1, RPS 적용 완료) |
| 구매요청서작성 다이얼로그 | ✅ | `components/sales/PurchaseRequestFormDialog.tsx` + 백엔드 `POST /api/sales/purchase-request` (savePurchaseRequest) |
| 품의서생성 액션 | ✅ | `components/sales/ProposalCreateDialog.tsx` + 백엔드 `POST /api/sales/purchase-request/:objid/proposal` (createProposalFromPurchaseReg) |
| 결재상신 (Amaranth SSO) | ✅ | 백엔드 `POST /api/sales/purchase-proposal/:objid/approval` (startProposalApproval) — target_type='PROPOSAL', formId=`AMARANTH_FORM_ID_PROPOSAL` 기본 `'1163'` |

### 6.1 신규 백엔드 엔드포인트

| Method | Path | 용도 |
|---|---|---|
| GET  | `/api/sales/purchase-request/mbom-parts?project_objid=…` | 프로젝트별 M-BOM 품목 (다이얼로그 자동 채움) |
| GET  | `/api/sales/purchase-request/:objid` | 헤더 + 라인 단건 |
| GET  | `/api/sales/purchase-request/:objid/proposal-targets` | 품의서 대상 품목 + 제외 품목 |
| POST | `/api/sales/purchase-request` | 신규/수정 UPSERT (라인 재생성) |
| POST | `/api/sales/purchase-request/:objid/proposal` | 품의서 생성 (PURCHASE_REG → PURCHASE_REG_PROPOSAL) |
| POST | `/api/sales/purchase-proposal/:objid/approval` | Amaranth SSO 결재상신 (TARGET_TYPE='PROPOSAL') |

### 6.2 환경변수 (신규)

| 키 | 기본값 | 비고 |
|---|---|---|
| `AMARANTH_FORM_ID_PROPOSAL` | `1163` | 품의서 결재 폼 ID |
| `AMARANTH_OUT_PROCESS_CODE_PROPOSAL` | — | 미설정 시 공통 `AMARANTH_OUT_PROCESS_CODE` fallback |
| `AMARANTH_COMP_SEQ` | `1000` | 영업 메뉴 전체 공통 |

### 6.3 데이터 흐름 (확정)

```
[구매요청서관리]                  [영업>품의서관리]                  [구매>품의서관리]
DOC_TYPE='PURCHASE_REG'           DOC_TYPE='PURCHASE_REG_PROPOSAL'   결재완료 시 자동 노출
SRM + SRP (라인)                  SRM(PROJECT_NO=원본 OBJID) + SRP(PROPOSAL_DATE=NOW)
                                                                    +
                            [품의서생성] 액션                       [발주서생성]
                            (단가+공급업체 입력 라인만)             (기존 구현)
                                                                    
                            [결재상신] Amaranth SSO
                            TARGET_TYPE='PROPOSAL', formId=1163
```

부모-자식 연결: 품의서 SRM 의 `PROJECT_NO` 컬럼이 원본 구매요청서 SRM 의 `OBJID`(varchar)를 담음.
원본 SRP 행의 `PROPOSAL_DATE` 가 채워지면 재생성 대상에서 제외.

## 7. 1차 스캐폴드 커밋

`7e7c6a0a` 영업관리 구매요청서관리·품의서관리 신규 2메뉴 (wace_plm 1:1)
- 6 files, +804 insertions

## 8. 검증 체크리스트

- [x] 백엔드/프론트 타입체크 통과 (사전부터 있던 multilang/zebraBluetooth 외 새 파일 0 에러)
- [ ] dev 서버 (8080/9771) 에서 두 메뉴 그리드 조회 동작 확인
- [ ] sales_request_master 에 `DOC_TYPE='PURCHASE_REG'` / `'PURCHASE_REG_PROPOSAL'` 행 존재 시 노출 확인 (현재 데이터 0건일 수 있음)
- [ ] 검색 필터 (구매유형/작성자/제품구분/작성일) 동작
- [ ] 구매관리>품의서관리 와 영업관리>품의서관리 가 동일 row 를 doc_type 기준으로 다르게 보여주는지 회귀 확인
