# 영업관리 G4/G11 — 수주 결재상신 (Amaranth 직행) 검증 > 작성: 2026-05-11 / 작성자: hjjeong > 목적: wace 영업관리 결재 흐름(외부 Amaranth SSO 직행)을 vexplor_rps 주문관리에 1:1 이식. chpark의 `amaranthApprovalClient`(자바 `AmaranthApprovalApiClient` 포팅) 재사용. ## 원본 출처 - 프론트: `wace_plm/WebContent/WEB-INF/view/contractMgmt/orderMgmtList.jsp:132~175` (btnApproval 가드) - 프론트: 같은 파일 `:547~576` (`fn_openAmaranthApproval` → SSO URL 호출 → window.open) - 백엔드: `wace_plm/src/com/pms/service/ApprovalService.java:1782~1909` (`getAmaranthSsoUrl`) - 매퍼: `wace_plm/src/com/pms/salesmgmt/mapper/contractMgmt.xml:523~530, 661~663` (ORDER_APPR_STATUS 라벨 + LEFT JOIN AMARANTH_APPROVAL) ## 이식 위치 - 백엔드 서비스: `backend-node/src/services/salesOrderMgmtService.ts:startOrderApproval` - 백엔드 컨트롤러: `backend-node/src/controllers/salesOrderMgmtController.ts:startApproval` - 라우트: `POST /api/sales/order-mgmt/:id/amaranth-approval` - 프론트 API: `frontend/lib/api/salesOrderMgmt.ts:startApproval` - 프론트 UI: `frontend/app/(main)/COMPANY_16/sales/order/page.tsx:handleAmaranthApproval` + "결재상신" 버튼 + `order_appr_status` 컬럼 - 재사용: `backend-node/src/services/amaranthApprovalClient.ts:getSsoUrl` (chpark) ## DB 스키마 변경 - `amaranth_approval.target_objid` BIGINT → **VARCHAR(80)** (wace 운영 패턴 1:1) - 출처: wace 매퍼 `T.OBJID::VARCHAR = AMR_ORDER.TARGET_OBJID` - 이유: vexplor_rps `contract_mgmt.objid`가 varchar(`CM-...` prefix 형식)라 bigint cast 불가 - 영향: 해당 테이블 데이터 0건이라 무손실. ECR/CS는 bigint값을 varchar에 INSERT해도 자동 cast → 무영향 - 마이그레이션 파일 `approvalTableMigration.ts`도 동기화 ## 결재 정책 (wace 1:1) - target_type: `CONTRACT_ORDER` (영업관리 주문서) - formId: `1161` (운영 amaranth 양식 ID) - compSeq: `1000` (운영 회사 시퀀스) - mod: `W` (Write) - empSeq 출처: `user_info.emp_seq` (PersonBean.getEmpseq 동등) — 미설정 시 명시적 에러 - approKey 분기: - 신규: `UB_` + Date.now().toString(36).toUpperCase() - 기존 reject/delete/create: 새 approKey + amaranth_approval UPDATE (재상신) - 기존 inProcess/complete: 기존 approKey 재사용 (프론트에서 차단되지만 백엔드 방어) ## 환경변수 (운영 배포 시 주입) | 변수 | 기본값 | 비고 | |---|---|---| | `AMARANTH_OUT_PROCESS_CODE_CONTRACT_ORDER` | (없음) | 수주 결재 전용 코드. 미설정 시 `AMARANTH_OUT_PROCESS_CODE` fallback | | `AMARANTH_FORM_ID_CONTRACT_ORDER` | `1161` | wace 운영값 | | `AMARANTH_COMP_SEQ` | `1000` | wace 운영값 | > Amaranth 외부 커넥션의 인증 정보(`baseUrl`/`groupSeq`/`callerName`/`accessToken`/`hashKey`/`aesKey`)는 chpark이 'Amaranth - 결재' 외부 커넥션 시드로 자동 주입 (별도 환경변수 불필요). ## 가드 (프론트 + 백엔드 동시) | 조건 | 메시지 | 처리 | |---|---|---| | 행 미선택 | "결재상신할 행을 선택해주십시오." | 프론트 toast | | `has_order_data === 0` | "수주 품목을 먼저 등록해주세요." | 프론트 toast + 백엔드 400 | | `order_amaranth_status === 'inProcess'` | "결재 진행중인 건은 상신할 수 없습니다." | 프론트 toast | | `order_amaranth_status === 'complete'` | "결재 완료된 건은 상신할 수 없습니다." | 프론트 toast | | 사용자 emp_seq 미설정 | "empSeq 정보가 없습니다." | 백엔드 400 | | 원본 contract_mgmt 부재 | "주문서를 찾을 수 없습니다." | 백엔드 404 | | SSO API resultCode != 0 | "결재 연동 오류: ..." | 백엔드 502 | ## 검증 SQL (BEGIN/ROLLBACK) ```sql BEGIN; INSERT INTO amaranth_approval (objid, target_objid, target_type, appro_key, status, form_id, comp_seq, emp_seq, writer, sso_url, regdate) VALUES (9999999999, '1256462102', 'CONTRACT_ORDER', 'UB_TEST', 'create', '1161', '1000', '999', 'test', 'http://test', NOW()); SELECT T.objid, T.contract_no, CASE WHEN AMR.status='complete' THEN '결재완료' WHEN AMR.status='inProcess' THEN '결재중' WHEN AMR.status='reject' THEN '반려' WHEN AMR.status='create' THEN '작성중' ELSE '' END AS order_appr_status FROM contract_mgmt T LEFT JOIN amaranth_approval AMR ON AMR.target_objid = T.objid AND AMR.target_type='CONTRACT_ORDER' WHERE T.objid='1256462102'; ROLLBACK; ``` ### 검증 결과 (2026-05-11) | target_objid | status | order_appr_status (한글) | |---|---|---| | `1256462102` | create | 작성중 | | `1256462102` | inProcess | 결재중 | | `1256462102` | complete | 결재완료 | | `1256462102` | reject | 반려 | | `CM-1778464096341-756` (varchar PK) | inProcess | 결재중 ✓ | - VARCHAR PK 호환 확인 — ALTER 효과 정상. - ROLLBACK 후 운영 데이터 영향 없음. ## API 호출 ```bash curl -X POST 'http://localhost:8080/api/sales/order-mgmt//amaranth-approval' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{"approvalTitle":"주문서 결재 - 26C-0800"}' # 성공: {"success":true,"data":{"fullUrl":"https://...","approKey":"UB_...","status":"create"}} # 실패: {"success":false,"message":"empSeq 정보가 없습니다. ..."} ``` ## UI 동작 1. 주문관리 그리드에서 행 1개 선택 2. "결재상신" 버튼 클릭 (수주복사 옆, 하늘색 sky-600) 3. 가드 통과 → 확인 다이얼로그: "결재상신 하시겠습니까?" 4. 확인 → API 호출 → `window.open(fullUrl, "amaranthApproval", "width=1200,height=900,...")` 5. 외부 Amaranth 결재 페이지에서 사용자가 양식 작성 + 상신 6. 목록 새로고침 → "결재상태" 컬럼이 '작성중' → '결재중' → '결재완료' 순으로 변화 ## 미구현 (백로그) - **첨부파일 원챔버 업로드** — wace `uploadOrderFilesToOneChamber` (영업관리 첨부 흐름 별도 작업 후 연동) - **견적 결재** (target_type=`CONTRACT_ESTIMATE`) — 같은 패턴, 견적관리 페이지에 추가만 하면 됨 (이번 PR 범위 외 — 현재 [estimate/page.tsx:474](../../../frontend/app/(main)/COMPANY_16/sales/estimate/page.tsx#L474) placeholder 토스트만 있음) - **결재 콜백** — amaranth가 우리 시스템에 결재 결과를 통보하는 webhook (운영에서는 폴링 또는 amaranth_approval 수동 갱신) ## 트러블슈팅 — Amaranth 운영 측 토큰 등록 (2026-05-11 확인) dev 환경에서 wace 계정(emp_seq=379) + 코드/HMAC 서명 모두 정상이지만 amaranth 서버가 다음 메시지로 거부: ``` 인증 토큰 발급 실패: API Proxy 호출 시 유효한 레디스 값이 존재하지 않습니다. ``` **진단**: - 우리 코드 흐름 정상 (`getAuthToken` → `api99u01A01` 호출까지 도달) - amaranth 서버가 `{resultCode:비-0, resultMsg:"...레디스..."}` 응답 → 토큰을 Redis 캐시에서 찾지 못함 - 7개 amaranth 커넥션 모두 같은 callerName(`API_gcmsAmaranth40578`)/groupSeq(`gcmsAmaranth40578`) 공유, accessToken만 도메인별로 다름 - 'Amaranth - 결재' 커넥션 시드 시점: 2026-05-08 12:16 (chpark 시드). 마지막 테스트: 2026-05-08 16:44 **가능한 원인 (운영 측 조치 필요)**: 1. 'Amaranth - 결재' 토큰만 amaranth 측 Redis 캐시에 등록 안 됨 (cron 배치로 매일 호출되는 다른 7개 커넥션은 정상 동작 가능성) 2. callerName이 wace_plm 운영과 공유되어 동시 사용 시 한쪽이 무효화 3. 결재 전용 토큰의 별도 갱신 주기 **대응**: - chpark에게 5/8 시드 시 amaranth 운영 측에 결재 토큰 등록을 마저 요청했는지 확인 - 또는 RPS ERP 담당자에게 결재 토큰 Redis 재등록 요청 - 우회 검증: 다른 amaranth cron 배치(예: 매일 03:10 사원 동기화)가 잘 도는지 확인 → 다른 건 성공이면 결재 토큰만 별도 등록 필요 확정 **코드 변경 없음** — 운영 협조로 해결되는 영역. 토큰 등록 완료되면 같은 흐름이 그대로 동작.