Files
wace_rps/docs/migration/sales/08-estimate-approval-verify.md
T
hjjeong b17d7b063d PR-D G11 견적 결재상신 — Amaranth 직행 (wace estimateList_new.jsp btnApproval 1:1)
G11 수주 결재상신(905d5c09)과 동일 패턴을 견적관리에 확장. target_type='CONTRACT_ESTIMATE',
target_objid=estimate_template.objid(최신 차수), formId='1162' (수주 1161과 별도 양식).

- 백엔드: salesEstimateService.startEstimateApproval + POST /sales/estimate/:id/amaranth-approval
- 견적 list SQL: LEFT JOIN amaranth_approval(CONTRACT_ESTIMATE) + APPR_STATUS 4단계 한글 라벨 + approval_required='N' fallback (wace contractMgmt.xml:513~522 1:1)
- 프론트: 견적관리 placeholder 토스트 → handleAmaranthApproval 핸들러 + sky-600 Send 버튼 (수주 페이지와 통일)
- docker-compose 3개: AMARANTH_OUT_PROCESS_CODE_CONTRACT_ESTIMATE + AMARANTH_FORM_ID_CONTRACT_ESTIMATE=1162 추가
- 가드: 행 미선택 / est_objid 없음(견적서 미작성) / inProcess+complete / notRequired+approval_required='N'
- 사전판정(checkApprovalRequired)은 G4 영역으로 분리 — 이번 PR은 단순 SSO 흐름만

검증: BEGIN/ROLLBACK으로 26C-0712(est_objid=-452406811) 4단계 상태(create→inProcess→complete→reject)
+ amaranth row 삭제 시 approval_required='N' fallback 모두 한글 라벨 정상. 문서 08-estimate-approval-verify.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 18:16:43 +09:00

8.7 KiB

영업관리 G4/G11 — 견적 결재상신 (Amaranth 직행) 검증

작성: 2026-05-11 / 작성자: hjjeong 목적: wace 영업관리 견적 결재 흐름(외부 Amaranth SSO 직행)을 vexplor_rps 견적관리에 1:1 이식. G11 수주 결재상신과 동일 패턴, target_type='CONTRACT_ESTIMATE' + target_objid=estimate_template.objid 차이.

원본 출처

  • 프론트: wace_plm/WebContent/WEB-INF/view/contractMgmt/estimateList_new.jsp:154~270 (btnApproval 가드 + checkApprovalRequired 사전판정)
  • 프론트: 같은 파일 :868~916 (fn_showApprovalConfirmSimple + fn_openAmaranthApproval → SSO URL 호출 → window.open)
  • 백엔드: wace_plm/src/com/pms/service/ApprovalService.java:1782~1909 (getAmaranthSsoUrl — CONTRACT_ESTIMATE 분기는 1853~1854)
  • 매퍼: wace_plm/src/com/pms/salesmgmt/mapper/contractMgmt.xml:513~522, 656~659 (APPR_STATUS 라벨 + LEFT JOIN AMARANTH_APPROVAL CONTRACT_ESTIMATE)

G11(수주) 대비 차이점

항목 수주 (G11) 견적 (이번 작업)
target_type CONTRACT_ORDER CONTRACT_ESTIMATE
target_objid contract_mgmt.objid (헤더 1개) estimate_template.objid (최신 차수)
formId 1161 1162
가드 has_order_data === 0 est_objid 없음(견적서 미작성)
사전판정 없음 wace는 checkApprovalRequired 분기 — G4 영역이라 이번 작업에서 제외

견적은 차수마다 별도 결재. 같은 영업번호의 차수1 결재 후 차수2 만들면 차수2는 다시 신규 amaranth_approval 매핑.

이식 위치

  • 백엔드 서비스: backend-node/src/services/salesEstimateService.ts:startEstimateApproval
  • 백엔드 컨트롤러: backend-node/src/controllers/salesEstimateController.ts:startApproval
  • 라우트: POST /api/sales/estimate/:id/amaranth-approval
  • 프론트 API: frontend/lib/api/salesEstimate.ts:startApproval
  • 프론트 UI: frontend/app/(main)/COMPANY_16/sales/estimate/page.tsx:handleAmaranthApproval + "결재상신" 버튼
  • 견적 list SQL 보강: 같은 파일 salesEstimateService.getListLEFT JOIN amaranth_approval AMR ON ET.objid::VARCHAR=AMR.target_objid AND AMR.target_type='CONTRACT_ESTIMATE' + APPR_STATUS/AMARANTH_STATUS CASE 분기
  • 재사용: backend-node/src/services/amaranthApprovalClient.ts:getSsoUrl (chpark, G11에서 도입)

결재 정책 (wace 1:1)

  • target_type: CONTRACT_ESTIMATE
  • formId: 1162 (운영 amaranth 견적 양식 ID — 수주 1161과 별도)
  • compSeq: 1000
  • mod: W
  • empSeq 출처: user_info.emp_seq
  • approKey 분기 (G11 동일):
    • 신규: UB_ + Date.now().toString(36).toUpperCase()
    • 기존 reject/delete/create: 새 approKey + amaranth_approval UPDATE (재상신)
    • 기존 inProcess/complete: 기존 approKey 재사용 (프론트에서 차단되지만 백엔드 방어)

환경변수 (운영 배포 시 주입)

변수 기본값 비고
AMARANTH_OUT_PROCESS_CODE_CONTRACT_ESTIMATE (없음) 견적 결재 전용 코드. 미설정 시 AMARANTH_OUT_PROCESS_CODE fallback
AMARANTH_FORM_ID_CONTRACT_ESTIMATE 1162 wace 운영값
AMARANTH_COMP_SEQ 1000 wace 운영값 (수주와 공유)

3개 docker-compose(deploy/onpremise, docker/deploy, docker/prod/docker-compose.backend.prod.yml) 모두 ${VAR:-default} 형식으로 매핑됨 — 호스트 .env에 아무것도 안 넣어도 wace 운영값 그대로 동작.

가드 (프론트 + 백엔드 동시)

조건 메시지 처리
행 미선택 "결재상신할 행을 선택해주십시오." 프론트 toast
est_objid 없음 "견적서를 먼저 작성해주세요." 프론트 toast + 백엔드 400
amaranth_status === 'inProcess' "결재 진행중인 건은 상신할 수 없습니다." 프론트 toast
amaranth_status === 'complete' "결재 완료된 건은 상신할 수 없습니다." 프론트 toast
amaranth_status === 'notRequired' 또는 approval_required === 'N' "결재불필요로 처리된 건입니다." 프론트 toast
사용자 emp_seq 미설정 "empSeq 정보가 없습니다." 백엔드 400
원본 contract_mgmt 부재 "견적을 찾을 수 없습니다." 백엔드 404
SSO API resultCode != 0 "결재 연동 오류: ..." 백엔드 502

검증 SQL (BEGIN/ROLLBACK)

BEGIN;

-- 가짜 amaranth_approval 매핑 INSERT (CONTRACT_ESTIMATE, target_objid = est_objid)
INSERT INTO amaranth_approval
  (objid, target_objid, target_type, appro_key, out_process_code, form_id,
   status, emp_seq, comp_seq, dept_seq, writer, sso_url, regdate)
VALUES
  (9999999998, '-452406811', 'CONTRACT_ESTIMATE', 'UB_TEST_EST', 'RPSPLM_00001', '1162',
   'create', '999', '1000', '', 'test', 'http://test', NOW());

-- 4단계 상태 라벨 확인 (UPDATE를 반복)
SELECT CASE
   WHEN AMR.status = 'complete'    THEN '결재완료'
   WHEN AMR.status = 'inProcess'   THEN '결재중'
   WHEN AMR.status = 'reject'      THEN '반려'
   WHEN AMR.status = 'create'      THEN '작성중'
   WHEN AMR.status = 'notRequired' THEN '결재불필요'
   WHEN COALESCE(T.APPROVAL_REQUIRED, 'N') = 'N' THEN '결재불필요'
   ELSE '' END AS appr_status, AMR.status, T.contract_no
  FROM contract_mgmt T
  LEFT JOIN LATERAL (
    SELECT objid FROM estimate_template
     WHERE contract_objid = T.objid
     ORDER BY regdate DESC LIMIT 1
  ) ET ON true
  LEFT JOIN amaranth_approval AMR
    ON AMR.target_objid = ET.objid::VARCHAR
   AND AMR.target_type  = 'CONTRACT_ESTIMATE'
 WHERE T.objid = '-912974684';

UPDATE amaranth_approval SET status='inProcess' WHERE objid=9999999998;
-- 위 SELECT 재실행 → '결재중'
UPDATE amaranth_approval SET status='complete' WHERE objid=9999999998;
-- → '결재완료'
UPDATE amaranth_approval SET status='reject' WHERE objid=9999999998;
-- → '반려'
DELETE FROM amaranth_approval WHERE objid=9999999998;
-- → '결재불필요' (approval_required='N' fallback)

ROLLBACK;

검증 결과 (2026-05-11)

샘플 contract: 26C-0712 (contract_objid=-912974684, est_objid=-452406811, approval_required='N')

AMR.status appr_status (한글)
create 작성중
inProcess 결재중
complete 결재완료
reject 반려
(AMR row 삭제, approval_required='N') 결재불필요
  • AMR row가 있으면 status 우선, 없으면 approval_required fallback — wace 1:1.
  • ROLLBACK 후 운영 데이터 영향 없음.
  • 견적 list SQL의 LEFT JOIN AMR 동작 확인 — 차수가 늘어나도 최신 차수만 매핑.

API 호출

curl -X POST 'http://localhost:8080/api/sales/estimate/<CONTRACT_OBJID>/amaranth-approval' \
  -H 'Authorization: Bearer <TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{"approvalTitle":"견적서 결재 - 26C-0712"}'

# 성공: {"success":true,"data":{"fullUrl":"https://...","approKey":"UB_...","status":"create","estObjid":"-452406811"}}
# 실패: {"success":false,"message":"견적서를 먼저 작성해주세요."}

UI 동작

  1. 견적관리 그리드에서 행 1개 선택
  2. "결재상신" 버튼 클릭 (메일발송 옆, sky-600)
  3. 가드 통과 → 확인 다이얼로그: "결재상신 하시겠습니까?"
  4. 확인 → API 호출 → window.open(fullUrl, "amaranthApproval", "width=1200,height=900,...")
  5. 외부 Amaranth 결재 페이지에서 사용자가 양식 작성 + 상신
  6. 목록 새로고침 → "결재상태" 컬럼이 '작성중' → '결재중' → '결재완료' 순으로 변화

미구현 (백로그)

  • 첨부파일 원챔버 업로드 — wace uploadEstimateFilesToOneChamber (영업관리 첨부 흐름 별도 작업 후 연동)
  • 사전판정 (checkApprovalRequired) — G4 영역. wace는 결재상신 클릭 → 재오더/신규수주/가격인하 룰 판정 → 재오더면 "결재불필요" 자동 처리, 신규수주/가격인하면 사유 안내 후 결재상신. 이번 작업은 G11 동일 흐름(단순 SSO)만.
  • 결재 콜백 — amaranth가 우리 시스템에 결재 결과를 통보하는 webhook (운영에서는 폴링 또는 amaranth_approval 수동 갱신)

운영 트러블슈팅 (G11과 공유)

dev에서 wace 계정으로 결재상신 클릭 시 amaranth가 다음 메시지로 거부 가능:

인증 토큰 발급 실패: API Proxy 호출 시 유효한 레디스 값이 존재하지 않습니다.

원인은 'Amaranth - 결재' 커넥션 accessToken이 amaranth 측 Redis 캐시에 등록 안 된 상태(7개 amaranth 커넥션 중 결재 토큰만 별도). G11 작업 시 동일 현상 확인됨. 대응 = chpark 또는 RPS ERP 담당자에게 결재 토큰 Redis 등록 요청 (자세한 진단: 07-amaranth-approval-verify.md 트러블슈팅 섹션).

코드 변경 없음 — 운영 협조로 해결되는 영역.