아마란스 결재 반영영
This commit is contained in:
@@ -820,6 +820,11 @@
|
||||
T.PROJECT_NO,
|
||||
T.CONTRACT_OBJID,
|
||||
T.SALES_DEADLINE_DATE,
|
||||
COALESCE(T.TAX_TYPE, '') AS TAX_TYPE,
|
||||
COALESCE(CODE_NAME(T.TAX_TYPE), '') AS TAX_TYPE_NAME,
|
||||
COALESCE(T.TAX_INVOICE_DATE, '') AS TAX_INVOICE_DATE,
|
||||
COALESCE(T.EXPORT_DECL_NO, '') AS EXPORT_DECL_NO,
|
||||
COALESCE(T.LOADING_DATE, '') AS LOADING_DATE,
|
||||
CODE_NAME(T.CATEGORY_CD) AS ORDER_TYPE,
|
||||
CODE_NAME(T.PRODUCT) AS PRODUCT_TYPE,
|
||||
CODE_NAME(T.AREA_CD) AS NATION,
|
||||
@@ -873,7 +878,7 @@
|
||||
(SELECT CM.PO_NO FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID) AS PO_NO,
|
||||
COALESCE(T.CONTRACT_DATE, (SELECT CM.order_date FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID)) AS ORDER_DATE,
|
||||
(SELECT COUNT(1) FROM ATTACH_FILE_INFO WHERE TARGET_OBJID = T.CONTRACT_OBJID AND DOC_TYPE IN ('FTC_ORDER', 'ORDER', 'ORDER_DOC') AND UPPER(STATUS) = 'ACTIVE') AS CU01_CNT,
|
||||
(SELECT CM.PRODUCTION_STATUS FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID) AS PRODUCTION_STATUS,
|
||||
COALESCE((SELECT SUM(PR.RESULT_QTY) FROM PRODUCTION_RESULT PR WHERE PR.PROJECT_OBJID = T.OBJID::VARCHAR AND PR.RESULT_TYPE = 'SHIP_WAIT' AND PR.STATUS = 'active'), 0) AS PRODUCTION_STATUS,
|
||||
-- 판매 관련 필드들 (sales_registration 테이블에서 한 번에 가져오기)
|
||||
COALESCE(SR.shipping_order_status, '') AS SHIPPING_ORDER_STATUS,
|
||||
-- 판매수량: 모든 분할 출하의 합계
|
||||
@@ -1584,7 +1589,7 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
|
||||
AND DOC_TYPE='ORDER_DOC'
|
||||
AND UPPER(STATUS) = 'ACTIVE'
|
||||
) THEN 'Y' ELSE 'N' END AS ORDER_ATTACH,
|
||||
(SELECT CM.PRODUCTION_STATUS FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID) AS PRODUCTION_STATUS,
|
||||
COALESCE((SELECT SUM(PR.RESULT_QTY) FROM PRODUCTION_RESULT PR WHERE PR.PROJECT_OBJID = T.OBJID::VARCHAR AND PR.RESULT_TYPE = 'SHIP_WAIT' AND PR.STATUS = 'active'), 0) AS PRODUCTION_STATUS,
|
||||
-- 판매 관련 필드들 (sales_registration 테이블에서 가져오기)
|
||||
SR.sale_no AS SALE_NO,
|
||||
COALESCE(SR.shipping_order_status, '') AS SHIPPING_ORDER_STATUS,
|
||||
@@ -2090,12 +2095,53 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
|
||||
COALESCE(CM.PO_NO, '') AS PO_NO,
|
||||
COALESCE(CM.ORDER_DATE, '') AS ORDER_DATE,
|
||||
COALESCE((SELECT SUM(SR.SALES_QUANTITY) FROM SALES_REGISTRATION SR WHERE SR.PROJECT_NO = T.PROJECT_NO), 0) AS SALES_QUANTITY,
|
||||
COALESCE(T.QUANTITY::NUMERIC, 0) - COALESCE((SELECT SUM(SR.SALES_QUANTITY) FROM SALES_REGISTRATION SR WHERE SR.PROJECT_NO = T.PROJECT_NO), 0) AS REMAINING_QUANTITY
|
||||
COALESCE(T.QUANTITY::NUMERIC, 0) - COALESCE((SELECT SUM(SR.SALES_QUANTITY) FROM SALES_REGISTRATION SR WHERE SR.PROJECT_NO = T.PROJECT_NO), 0) AS REMAINING_QUANTITY,
|
||||
COALESCE(NULLIF(CM.ORDER_UNIT_PRICE, '')::NUMERIC, 0) AS ORDER_UNIT_PRICE,
|
||||
COALESCE(NULLIF(CM.EXCHANGE_RATE, '')::NUMERIC, 1) AS EXCHANGE_RATE,
|
||||
COALESCE(CM.CONTRACT_CURRENCY, '') AS CONTRACT_CURRENCY
|
||||
FROM PROJECT_MGMT T
|
||||
LEFT JOIN CONTRACT_MGMT CM ON CM.OBJID = T.CONTRACT_OBJID
|
||||
LEFT JOIN SUPPLY_MNG SM ON SM.OBJID = CASE WHEN T.CUSTOMER_OBJID ~ '^[0-9]+$' THEN T.CUSTOMER_OBJID::NUMERIC ELSE NULL END
|
||||
WHERE T.PROJECT_NO = #{projectNo}
|
||||
</select>
|
||||
|
||||
<!--
|
||||
/**
|
||||
* 마감정보 조회
|
||||
* @since 2026.02.13
|
||||
* @author system
|
||||
* @version 1.0
|
||||
**/
|
||||
-->
|
||||
<select id="getDeadlineInfo" parameterType="map" resultType="map">
|
||||
/* salesNcollectMgmt.getDeadlineInfo - 마감정보 조회 */
|
||||
SELECT
|
||||
COALESCE(TAX_TYPE, '') AS TAX_TYPE,
|
||||
COALESCE(TAX_INVOICE_DATE, '') AS TAX_INVOICE_DATE,
|
||||
COALESCE(EXPORT_DECL_NO, '') AS EXPORT_DECL_NO,
|
||||
COALESCE(LOADING_DATE, '') AS LOADING_DATE
|
||||
FROM PROJECT_MGMT
|
||||
WHERE OBJID::VARCHAR = #{OBJID}
|
||||
</select>
|
||||
|
||||
<!--
|
||||
/**
|
||||
* 마감정보 저장
|
||||
* @since 2026.02.13
|
||||
* @author system
|
||||
* @version 1.0
|
||||
**/
|
||||
-->
|
||||
<update id="saveDeadlineInfo" parameterType="map">
|
||||
/* salesNcollectMgmt.saveDeadlineInfo - 마감정보 저장 */
|
||||
UPDATE PROJECT_MGMT
|
||||
SET
|
||||
TAX_TYPE = #{taxType},
|
||||
TAX_INVOICE_DATE = #{taxInvoiceDate},
|
||||
EXPORT_DECL_NO = #{exportDeclNo},
|
||||
LOADING_DATE = #{loadingDate}
|
||||
WHERE OBJID::VARCHAR = #{OBJID}
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
|
||||
|
||||
@@ -182,7 +182,9 @@ function fn_openProposalFormPopUp(objId){
|
||||
|
||||
// Amaranth10 전자결재 SSO 팝업 열기
|
||||
function fn_openAmaranthApproval(objId){
|
||||
var title = encodeURIComponent("품의서 결재");
|
||||
var selectedData = _tabulGrid.getSelectedData();
|
||||
var proposalNo = fnc_checkNull(selectedData[0].PROPOSAL_NO);
|
||||
var title = "품의서 결재" + (proposalNo ? " - " + proposalNo : "");
|
||||
|
||||
$.ajax({
|
||||
url: "/approval/getAmaranthSsoUrl.do",
|
||||
@@ -191,7 +193,7 @@ function fn_openAmaranthApproval(objId){
|
||||
"targetType": "PROPOSAL",
|
||||
"targetObjId": objId,
|
||||
"approvalTitle": title,
|
||||
"outProcessCode": "",
|
||||
"outProcessCode": "${AMARANTH_OUT_PROCESS_CODE}",
|
||||
"formId": "",
|
||||
"compSeq": "1000",
|
||||
"deptSeq": ""
|
||||
|
||||
@@ -31,7 +31,7 @@ public class AmaranthApprovalApiClient {
|
||||
private static final String ACCESS_TOKEN = "MN5KzKBWRAa92BPxDlRLl3GcsxeZXc";
|
||||
private static final String HASH_KEY = "22519103205540290721741689643674301018832465";
|
||||
private static final String GROUP_SEQ = "gcmsAmaranth40578";
|
||||
private static final String AES_KEY = "gcmsAmaranth40578"; // SSO 암호화 키
|
||||
private static final String AES_KEY = "8441e27489d402cd"; // SSO 암호화 키 (API상품연동설정에서 확인)
|
||||
|
||||
/**
|
||||
* 인증 토큰 발급
|
||||
@@ -827,11 +827,13 @@ public class AmaranthApprovalApiClient {
|
||||
* @param mod 작성/보기/삭제 구분 (W:작성, V:보기, D:삭제)
|
||||
* @param compSeq 회사 시퀀스 (선택, 겸직별 결재 시 필요)
|
||||
* @param deptSeq 부서 시퀀스 (선택, 겸직별 결재 시 필요)
|
||||
* @param loginId 로그인 아이디 (appParams에 포함, 선택)
|
||||
* @return API 응답 JSON (resultData.fullUrl 포함)
|
||||
*/
|
||||
public String getSsoUrl(String baseUrl, String empSeq, String outProcessCode,
|
||||
String formId, String approKey, String subjectStr,
|
||||
String mod, String compSeq, String deptSeq) throws Exception {
|
||||
String mod, String compSeq, String deptSeq,
|
||||
String loginId) throws Exception {
|
||||
|
||||
System.out.println("=== Amaranth SSO URL 생성 시작 ===");
|
||||
System.out.println("empSeq: " + empSeq + ", outProcessCode: " + outProcessCode + ", formId: " + formId);
|
||||
@@ -885,7 +887,7 @@ public class AmaranthApprovalApiClient {
|
||||
|
||||
// 요청 본문 구성
|
||||
String requestBody = buildSsoRequestBody(empSeqEnc, outProcessCode, formId,
|
||||
approKey, subjectStr, mod, compSeq, deptSeq);
|
||||
approKey, subjectStr, mod, compSeq, deptSeq, loginId);
|
||||
|
||||
System.out.println("[2단계] SSO API 호출 - URL: " + fullUrl);
|
||||
System.out.println("[2단계] Request Body: " + requestBody);
|
||||
@@ -940,7 +942,8 @@ public class AmaranthApprovalApiClient {
|
||||
private String buildSsoRequestBody(String empSeqEnc, String outProcessCode,
|
||||
String formId, String approKey,
|
||||
String subjectStr, String mod,
|
||||
String compSeq, String deptSeq) {
|
||||
String compSeq, String deptSeq,
|
||||
String loginId) {
|
||||
StringBuilder json = new StringBuilder();
|
||||
json.append("{");
|
||||
json.append("\"header\":{},");
|
||||
@@ -965,6 +968,11 @@ public class AmaranthApprovalApiClient {
|
||||
json.append("\"approKey\":\"").append(escapeJson(approKey)).append("\",");
|
||||
json.append("\"mod\":\"").append(escapeJson(mod)).append("\"");
|
||||
|
||||
// loginId (가이드 예제에 따라 appParams에 포함)
|
||||
if (loginId != null && !loginId.isEmpty()) {
|
||||
json.append(",\"loginId\":\"").append(escapeJson(loginId)).append("\"");
|
||||
}
|
||||
|
||||
// outProcessCode 또는 formId (둘 중 하나 필수)
|
||||
if (outProcessCode != null && !outProcessCode.isEmpty()) {
|
||||
json.append(",\"outProcessCode\":\"").append(escapeJson(outProcessCode)).append("\"");
|
||||
@@ -978,6 +986,9 @@ public class AmaranthApprovalApiClient {
|
||||
json.append(",\"subjectStr\":\"").append(escapeJson(subjectStr)).append("\"");
|
||||
}
|
||||
|
||||
// 본문 인코딩 (UTF-8)
|
||||
json.append(",\"contentsEnc\":\"U\"");
|
||||
|
||||
json.append("}"); // appParams 닫기
|
||||
json.append("}"); // body 닫기
|
||||
json.append("}"); // root 닫기
|
||||
|
||||
@@ -76,6 +76,10 @@ public class Constants {
|
||||
// sql config
|
||||
public static final String MYBATIS_CONFIG_FILE_PATH = "./com/pms/mapper/mybatisConf.xml";
|
||||
|
||||
// Amaranth10 전자결재 SSO 연동 설정
|
||||
// 결재연동설정에서 등록한 외부시스템 결재연동코드 (Amaranth 관리자 > 결재프로세스관리 > 결재연동설정)
|
||||
public static final String AMARANTH_OUT_PROCESS_CODE = "RPSPLM_00001";
|
||||
|
||||
//AES 암호화 키
|
||||
public static final String keyName = "ILJIAESSECRETKEY";
|
||||
public static final String algorithm = "AES";
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import com.pms.common.SqlMapConfig;
|
||||
import com.pms.common.bean.PersonBean;
|
||||
@@ -388,29 +389,21 @@ public class ApprovalController {
|
||||
* Amaranth10 전자결재 콜백 API
|
||||
* 결재 이벤트(상신/진행/종결/반려/삭제 등) 발생 시 Amaranth10에서 호출
|
||||
* 결재연동설정의 상신~삭제 URL에 이 엔드포인트를 등록
|
||||
* @param request
|
||||
* @param paramMap processId, approkey, docId, docSts, userId, formId, docTitle 등
|
||||
* @return SUCCESS 응답 JSON
|
||||
*/
|
||||
@RequestMapping("/approval/amaranthApprovalCallback.do")
|
||||
@ResponseBody
|
||||
@RequestMapping(value="/approval/amaranthApprovalCallback.do", produces="application/json; charset=UTF-8")
|
||||
public String amaranthApprovalCallback(HttpServletRequest request, @RequestParam Map<String, Object> paramMap)throws Exception{
|
||||
String jsonResult = approvalService.handleAmaranthApprovalCallback(paramMap);
|
||||
request.setAttribute("RESULT", jsonResult);
|
||||
return "/ajax/ajaxResult";
|
||||
return approvalService.handleAmaranthApprovalCallback(paramMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Amaranth10 전자결재 본문 조회 API (결재작성 시 호출)
|
||||
* 결재연동설정의 결재작성 URL에 이 엔드포인트를 등록
|
||||
* @param request
|
||||
* @param paramMap approkey, formId, docId, userId, empSeq 등
|
||||
* @return 제목/본문 JSON
|
||||
* 결재연동설정의 결재작성(WebAPI) URL에 이 엔드포인트를 등록
|
||||
*/
|
||||
@RequestMapping("/approval/amaranthApprovalContents.do")
|
||||
@ResponseBody
|
||||
@RequestMapping(value="/approval/amaranthApprovalContents.do", produces="application/json; charset=UTF-8")
|
||||
public String amaranthApprovalContents(HttpServletRequest request, @RequestParam Map<String, Object> paramMap)throws Exception{
|
||||
String jsonResult = approvalService.getAmaranthApprovalContents(paramMap);
|
||||
request.setAttribute("RESULT", jsonResult);
|
||||
return "/ajax/ajaxResult";
|
||||
return approvalService.getAmaranthApprovalContents(paramMap);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -606,4 +606,59 @@
|
||||
WHERE OBJID = #{estObjId}::NUMERIC
|
||||
</select>
|
||||
|
||||
<!-- =====================================================
|
||||
Amaranth10 전자결재 SSO 연동 관련 SQL
|
||||
|
||||
[사전 작업] 아래 DDL을 DB에서 실행해야 합니다:
|
||||
CREATE TABLE IF NOT EXISTS AMARANTH_APPROVAL (
|
||||
OBJID SERIAL PRIMARY KEY,
|
||||
APPRO_KEY VARCHAR(100) NOT NULL,
|
||||
TARGET_TYPE VARCHAR(50),
|
||||
TARGET_OBJID VARCHAR(50),
|
||||
APPROVAL_TITLE VARCHAR(500),
|
||||
WRITER VARCHAR(50),
|
||||
AMARANTH_DOC_ID VARCHAR(50),
|
||||
DOC_STS VARCHAR(10) DEFAULT '0',
|
||||
STATUS VARCHAR(20) DEFAULT 'create',
|
||||
REGDATE TIMESTAMP DEFAULT NOW(),
|
||||
UPDATE_DATE TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS IDX_AMARANTH_APPROVAL_APPRO_KEY ON AMARANTH_APPROVAL(APPRO_KEY);
|
||||
===================================================== -->
|
||||
|
||||
<!-- Amaranth 결재 매핑 등록 (SSO URL 생성 시) -->
|
||||
<insert id="insertAmaranthApproval" parameterType="map">
|
||||
INSERT INTO AMARANTH_APPROVAL (
|
||||
APPRO_KEY, TARGET_TYPE, TARGET_OBJID, APPROVAL_TITLE, WRITER, STATUS, REGDATE
|
||||
) VALUES (
|
||||
#{approKey}, #{targetType}, #{targetObjId}, #{approvalTitle}, #{writer}, 'create', NOW()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<!-- approKey로 Amaranth 결재 매핑 조회 -->
|
||||
<select id="selectAmaranthApprovalByApproKey" parameterType="map" resultType="map">
|
||||
SELECT
|
||||
OBJID, APPRO_KEY, TARGET_TYPE, TARGET_OBJID, APPROVAL_TITLE,
|
||||
WRITER, AMARANTH_DOC_ID, DOC_STS, STATUS, REGDATE, UPDATE_DATE
|
||||
FROM AMARANTH_APPROVAL
|
||||
WHERE APPRO_KEY = #{approKey}
|
||||
</select>
|
||||
|
||||
<!-- Amaranth 콜백에 의한 상태 업데이트 -->
|
||||
<update id="updateAmaranthApprovalByCallback" parameterType="map">
|
||||
UPDATE AMARANTH_APPROVAL SET
|
||||
AMARANTH_DOC_ID = #{amaranthDocId},
|
||||
DOC_STS = #{docSts},
|
||||
STATUS = #{status},
|
||||
UPDATE_DATE = NOW()
|
||||
WHERE APPRO_KEY = #{approKey}
|
||||
</update>
|
||||
|
||||
<!-- 품의서(SALES_REQUEST_MASTER) 결재 상태 변경 -->
|
||||
<update id="changeProposalApprovalStatus" parameterType="map">
|
||||
UPDATE SALES_REQUEST_MASTER SET
|
||||
STATUS = #{status}
|
||||
WHERE OBJID::VARCHAR = #{targetObjId}::VARCHAR
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
@@ -1369,6 +1369,8 @@ public class SalesMngController {
|
||||
e.printStackTrace();
|
||||
}
|
||||
request.setAttribute("code_map", code_map);
|
||||
// Amaranth10 전자결재 연동코드 (결재연동설정에서 등록한 코드)
|
||||
request.setAttribute("AMARANTH_OUT_PROCESS_CODE", com.pms.common.utils.Constants.AMARANTH_OUT_PROCESS_CODE);
|
||||
return "/salesMng/purchaseRegProposalMngList";
|
||||
}
|
||||
|
||||
|
||||
@@ -1779,6 +1779,7 @@ public class ApprovalService {
|
||||
* @return fullUrl이 포함된 JSON 문자열
|
||||
*/
|
||||
public String getAmaranthSsoUrl(HttpServletRequest request, Map paramMap){
|
||||
SqlSession sqlSession = null;
|
||||
try {
|
||||
HttpSession session = request.getSession();
|
||||
PersonBean person = (PersonBean)session.getAttribute(Constants.PERSON_BEAN);
|
||||
@@ -1787,13 +1788,15 @@ public class ApprovalService {
|
||||
}
|
||||
|
||||
String empSeq = CommonUtils.checkNull(person.getEmpseq());
|
||||
String loginId = CommonUtils.checkNull(person.getUserId());
|
||||
|
||||
System.out.println("=== Amaranth SSO - 사용자 정보 ===");
|
||||
System.out.println("userId: " + person.getUserId());
|
||||
System.out.println("userId(loginId): " + loginId);
|
||||
System.out.println("userName: " + person.getUserName());
|
||||
System.out.println("empSeq: [" + empSeq + "]");
|
||||
|
||||
if(empSeq.isEmpty()){
|
||||
return "{\"resultCode\":-1,\"resultMsg\":\"empSeq 정보가 없습니다. (userId: " + person.getUserId() + ") 관리자에게 문의하세요.\"}";
|
||||
return "{\"resultCode\":-1,\"resultMsg\":\"empSeq 정보가 없습니다. (userId: " + loginId + ") 관리자에게 문의하세요.\"}";
|
||||
}
|
||||
|
||||
// 파라미터 추출
|
||||
@@ -1809,25 +1812,38 @@ public class ApprovalService {
|
||||
String approKey = "UB_" + java.util.UUID.randomUUID().toString();
|
||||
|
||||
System.out.println("=== Amaranth SSO URL 생성 ===");
|
||||
System.out.println("empSeq: " + empSeq);
|
||||
System.out.println("empSeq: " + empSeq + ", loginId: " + loginId);
|
||||
System.out.println("targetType: " + targetType + ", targetObjId: " + targetObjId);
|
||||
System.out.println("outProcessCode: " + outProcessCode + ", formId: " + formId);
|
||||
System.out.println("approKey: " + approKey);
|
||||
|
||||
// API 호출
|
||||
// API 호출 (loginId 파라미터 추가)
|
||||
com.pms.api.AmaranthApprovalApiClient apiClient = new com.pms.api.AmaranthApprovalApiClient();
|
||||
String apiResponse = apiClient.getSsoUrl(
|
||||
AMARANTH_BASE_URL, empSeq, outProcessCode, formId,
|
||||
approKey, approvalTitle, "W", compSeq, deptSeq
|
||||
approKey, approvalTitle, "W", compSeq, deptSeq, loginId
|
||||
);
|
||||
|
||||
System.out.println("SSO API 응답: " + apiResponse);
|
||||
|
||||
// 응답에서 fullUrl 추출하여 approKey와 함께 반환
|
||||
// 응답에서 fullUrl 추출
|
||||
String fullUrl = extractJsonStringValue(apiResponse, "fullUrl");
|
||||
String resultCode = extractJsonStringValue(apiResponse, "resultCode");
|
||||
|
||||
if("0".equals(resultCode) && !fullUrl.isEmpty()){
|
||||
// approKey → targetObjId 매핑을 DB에 저장 (콜백/컨텐츠 조회 시 사용)
|
||||
sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
|
||||
Map<String, Object> mappingParam = new HashMap();
|
||||
mappingParam.put("approKey", approKey);
|
||||
mappingParam.put("targetType", targetType);
|
||||
mappingParam.put("targetObjId", targetObjId);
|
||||
mappingParam.put("approvalTitle", approvalTitle);
|
||||
mappingParam.put("writer", loginId);
|
||||
sqlSession.insert("approval.insertAmaranthApproval", mappingParam);
|
||||
sqlSession.commit();
|
||||
|
||||
System.out.println("Amaranth 결재 매핑 DB 저장 완료 - approKey: " + approKey);
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append("{\"resultCode\":0,\"resultMsg\":\"SUCCESS\",\"resultData\":{");
|
||||
result.append("\"fullUrl\":\"").append(escapeJsonValue(fullUrl)).append("\",");
|
||||
@@ -1842,9 +1858,12 @@ public class ApprovalService {
|
||||
}
|
||||
|
||||
} catch(Exception e){
|
||||
if(sqlSession != null) sqlSession.rollback();
|
||||
System.err.println("Amaranth SSO URL 생성 오류: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return "{\"resultCode\":-1,\"resultMsg\":\"" + escapeJsonValue(e.getMessage()) + "\"}";
|
||||
} finally {
|
||||
if(sqlSession != null) sqlSession.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1862,6 +1881,7 @@ public class ApprovalService {
|
||||
String docTitle = CommonUtils.checkNull(paramMap.get("docTitle"));
|
||||
String userId = CommonUtils.checkNull(paramMap.get("userId"));
|
||||
String processId = CommonUtils.checkNull(paramMap.get("processId"));
|
||||
String appCancelYn = CommonUtils.checkNull(paramMap.get("appCancelYn"), "0");
|
||||
|
||||
System.out.println("=== Amaranth 결재 콜백 수신 ===");
|
||||
System.out.println("approkey: " + approkey);
|
||||
@@ -1870,14 +1890,97 @@ public class ApprovalService {
|
||||
System.out.println("docTitle: " + docTitle);
|
||||
System.out.println("userId: " + userId);
|
||||
System.out.println("processId: " + processId);
|
||||
System.out.println("appCancelYn: " + appCancelYn);
|
||||
System.out.println("전체 파라미터: " + paramMap);
|
||||
|
||||
// TODO: approkey로 우리 시스템의 대상 문서를 찾아서 상태 업데이트
|
||||
// 예: approval 테이블에서 approkey로 조회 → targetObjId의 상태 변경
|
||||
SqlSession sqlSession = null;
|
||||
try {
|
||||
sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
|
||||
|
||||
// approkey로 매핑 정보 조회
|
||||
Map<String, Object> searchParam = new HashMap();
|
||||
searchParam.put("approKey", approkey);
|
||||
Map<String, Object> mappingInfo = sqlSession.selectOne("approval.selectAmaranthApprovalByApproKey", searchParam);
|
||||
|
||||
if(mappingInfo == null){
|
||||
System.err.println("콜백 처리 실패: approkey에 해당하는 매핑 정보 없음 - " + approkey);
|
||||
return "{\"resultCode\":\"SUCCESS\",\"resultMessage\":\"성공하였습니다.\"}";
|
||||
}
|
||||
|
||||
String targetType = CommonUtils.checkNull(mappingInfo.get("target_type"));
|
||||
String targetObjId = CommonUtils.checkNull(mappingInfo.get("target_objid"));
|
||||
|
||||
System.out.println("매핑 정보 - targetType: " + targetType + ", targetObjId: " + targetObjId);
|
||||
|
||||
// Amaranth 결재 매핑 테이블 상태 업데이트
|
||||
String internalStatus = convertDocStsToInternalStatus(docSts, appCancelYn);
|
||||
Map<String, Object> updateParam = new HashMap();
|
||||
updateParam.put("approKey", approkey);
|
||||
updateParam.put("amaranthDocId", docId);
|
||||
updateParam.put("docSts", docSts);
|
||||
updateParam.put("status", internalStatus);
|
||||
sqlSession.update("approval.updateAmaranthApprovalByCallback", updateParam);
|
||||
|
||||
// 대상 문서(품의서 등)의 상태 업데이트
|
||||
if(!targetObjId.isEmpty()){
|
||||
String proposalStatus = convertDocStsToProposalStatus(docSts, appCancelYn);
|
||||
if(!proposalStatus.isEmpty()){
|
||||
Map<String, Object> statusParam = new HashMap();
|
||||
statusParam.put("targetObjId", targetObjId);
|
||||
statusParam.put("status", proposalStatus);
|
||||
|
||||
if("PROPOSAL".equals(targetType)){
|
||||
sqlSession.update("approval.changeProposalApprovalStatus", statusParam);
|
||||
System.out.println("품의서 상태 변경 - targetObjId: " + targetObjId + " → " + proposalStatus);
|
||||
} else if("SALES_REQUEST".equals(targetType)){
|
||||
sqlSession.update("approval.salesRequestApprovalStatus", statusParam);
|
||||
System.out.println("구매요청 상태 변경 - targetObjId: " + targetObjId + " → " + proposalStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sqlSession.commit();
|
||||
System.out.println("콜백 처리 완료 - docSts: " + docSts + " → internalStatus: " + internalStatus);
|
||||
|
||||
} catch(Exception e){
|
||||
if(sqlSession != null) sqlSession.rollback();
|
||||
System.err.println("콜백 처리 중 오류: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if(sqlSession != null) sqlSession.close();
|
||||
}
|
||||
|
||||
return "{\"resultCode\":\"SUCCESS\",\"resultMessage\":\"성공하였습니다.\"}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Amaranth docSts → 내부 상태 변환
|
||||
* 10:임시보관, 20:상신, 30:진행, 90:종결, 100:반려, 110:보류
|
||||
*/
|
||||
private String convertDocStsToInternalStatus(String docSts, String appCancelYn){
|
||||
if("1".equals(appCancelYn)) return "create";
|
||||
if("10".equals(docSts)) return "create";
|
||||
if("20".equals(docSts)) return "inProcess";
|
||||
if("30".equals(docSts)) return "inProcess";
|
||||
if("90".equals(docSts)) return "complete";
|
||||
if("100".equals(docSts)) return "reject";
|
||||
if("110".equals(docSts)) return "hold";
|
||||
return "inProcess";
|
||||
}
|
||||
|
||||
/**
|
||||
* Amaranth docSts → 품의서(SALES_REQUEST_MASTER) STATUS 변환
|
||||
*/
|
||||
private String convertDocStsToProposalStatus(String docSts, String appCancelYn){
|
||||
if("1".equals(appCancelYn)) return "create";
|
||||
if("20".equals(docSts)) return "approvalRequest";
|
||||
if("30".equals(docSts)) return "approvalRequest";
|
||||
if("90".equals(docSts)) return "approvalComplete";
|
||||
if("100".equals(docSts)) return "reject";
|
||||
if("110".equals(docSts)) return "approvalRequest";
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Amaranth10 결재작성 시 본문 내용 조회 (Binding WebAPI / WebAPI)
|
||||
* 결재 작성 화면이 열릴 때 Amaranth10에서 호출
|
||||
@@ -1894,15 +1997,187 @@ public class ApprovalService {
|
||||
System.out.println("docId: " + docId);
|
||||
System.out.println("전체 파라미터: " + paramMap);
|
||||
|
||||
// TODO: approkey로 대상 문서 데이터를 조회하여 본문 HTML 또는 Binding JSON 구성
|
||||
// Binding WebAPI 방식일 경우 ITEMS/TABLE 구조의 JSON 반환
|
||||
SqlSession sqlSession = null;
|
||||
try {
|
||||
sqlSession = SqlMapConfig.getInstance().getSqlSession();
|
||||
|
||||
// approkey로 매핑 정보 조회
|
||||
Map<String, Object> searchParam = new HashMap();
|
||||
searchParam.put("approKey", approkey);
|
||||
Map<String, Object> mappingInfo = sqlSession.selectOne("approval.selectAmaranthApprovalByApproKey", searchParam);
|
||||
|
||||
String title = "결재 문서";
|
||||
String contentsHtml = "<p>연동 데이터를 찾을 수 없습니다.</p>";
|
||||
|
||||
if(mappingInfo != null){
|
||||
String targetType = CommonUtils.checkNull(mappingInfo.get("target_type"));
|
||||
String targetObjId = CommonUtils.checkNull(mappingInfo.get("target_objid"));
|
||||
title = CommonUtils.checkNull(mappingInfo.get("approval_title"), "결재 문서");
|
||||
|
||||
System.out.println("매핑 정보 - targetType: " + targetType + ", targetObjId: " + targetObjId);
|
||||
|
||||
if("PROPOSAL".equals(targetType) && !targetObjId.isEmpty()){
|
||||
// 품의서 데이터 조회
|
||||
Map<String, Object> proposalParam = new HashMap();
|
||||
proposalParam.put("PROPOSAL_OBJID", targetObjId);
|
||||
Map<String, Object> proposalInfo = sqlSession.selectOne("salesMng.getProposalInfo", proposalParam);
|
||||
|
||||
if(proposalInfo != null){
|
||||
proposalInfo = CommonUtils.toUpperCaseMapKey(proposalInfo);
|
||||
contentsHtml = buildProposalContentsHtml(proposalInfo, sqlSession, targetObjId);
|
||||
|
||||
String proposalNo = CommonUtils.checkNull(proposalInfo.get("PROPOSAL_NO"));
|
||||
if(!proposalNo.isEmpty()){
|
||||
title = "품의서 결재 - " + proposalNo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UTF-8 인코딩 처리 (contentsEnc=U 설정됨)
|
||||
String encodedTitle = java.net.URLEncoder.encode(title, "UTF-8");
|
||||
String encodedContents = java.net.URLEncoder.encode(contentsHtml, "UTF-8");
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append("{\"resultCode\":0,\"resultMessage\":\"SUCCESS\",\"resultData\":{");
|
||||
result.append("\"title\":\"").append(escapeJsonValue(encodedTitle)).append("\",");
|
||||
result.append("\"contents\":\"").append(escapeJsonValue(encodedContents)).append("\"");
|
||||
result.append("}}");
|
||||
return result.toString();
|
||||
|
||||
} catch(Exception e){
|
||||
System.err.println("결재 본문 조회 오류: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append("{\"resultCode\":0,\"resultMessage\":\"SUCCESS\",\"resultData\":{");
|
||||
result.append("\"title\":\"결재 문서\",");
|
||||
result.append("\"contents\":\"<p>본문 조회 중 오류가 발생했습니다.</p>\"");
|
||||
result.append("}}");
|
||||
return result.toString();
|
||||
} finally {
|
||||
if(sqlSession != null) sqlSession.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 품의서 데이터를 HTML 본문으로 구성
|
||||
*/
|
||||
private String buildProposalContentsHtml(Map proposalInfo, SqlSession sqlSession, String targetObjId){
|
||||
StringBuilder html = new StringBuilder();
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append("{\"resultCode\":0,\"resultMessage\":\"SUCCESS\",\"resultData\":{");
|
||||
result.append("\"title\":\"결재 문서\",");
|
||||
result.append("\"contents\":\"<p>본문 내용</p>\"");
|
||||
result.append("}}");
|
||||
return result.toString();
|
||||
String proposalNo = CommonUtils.checkNull(proposalInfo.get("PROPOSAL_NO"));
|
||||
String projectNumber = CommonUtils.checkNull(proposalInfo.get("PROJECT_NUMBER"));
|
||||
String projectName = CommonUtils.checkNull(proposalInfo.get("PROJECT_NAME"));
|
||||
String purchaseTypeName = CommonUtils.checkNull(proposalInfo.get("PURCHASE_TYPE_NAME"));
|
||||
String orderTypeName = CommonUtils.checkNull(proposalInfo.get("ORDER_TYPE_NAME"));
|
||||
String productName = CommonUtils.checkNull(proposalInfo.get("PRODUCT_NAME_TITLE"));
|
||||
String customerName = CommonUtils.checkNull(proposalInfo.get("PROJECT_CUSTOMER_NAME"));
|
||||
String writerName = CommonUtils.checkNull(proposalInfo.get("WRITER_NAME"));
|
||||
String regdate = CommonUtils.checkNull(proposalInfo.get("REGDATE_TITLE"));
|
||||
String remark = CommonUtils.checkNull(proposalInfo.get("REMARK"));
|
||||
String totalAmount = CommonUtils.checkNull(proposalInfo.get("TOTAL_AMOUNT"));
|
||||
|
||||
html.append("<div style='font-family:맑은 고딕,Malgun Gothic,sans-serif;font-size:10pt;'>");
|
||||
|
||||
html.append("<table cellspacing='0' cellpadding='5' style='border-collapse:collapse;width:100%;'>");
|
||||
html.append("<colgroup><col style='width:120px;'/><col style='width:200px;'/><col style='width:120px;'/><col style='width:200px;'/></colgroup>");
|
||||
html.append("<tbody>");
|
||||
|
||||
html.append("<tr>");
|
||||
html.append("<th style='border:1px solid #333;background-color:#E8E8E8;text-align:center;font-weight:bold;'>품의서 No</th>");
|
||||
html.append("<td style='border:1px solid #333;text-align:left;padding-left:8px;'>").append(escapeHtml(proposalNo)).append("</td>");
|
||||
html.append("<th style='border:1px solid #333;background-color:#E8E8E8;text-align:center;font-weight:bold;'>작성일</th>");
|
||||
html.append("<td style='border:1px solid #333;text-align:center;'>").append(escapeHtml(regdate)).append("</td>");
|
||||
html.append("</tr>");
|
||||
|
||||
html.append("<tr>");
|
||||
html.append("<th style='border:1px solid #333;background-color:#E8E8E8;text-align:center;font-weight:bold;'>프로젝트번호</th>");
|
||||
html.append("<td style='border:1px solid #333;text-align:left;padding-left:8px;'>").append(escapeHtml(projectNumber)).append("</td>");
|
||||
html.append("<th style='border:1px solid #333;background-color:#E8E8E8;text-align:center;font-weight:bold;'>프로젝트명</th>");
|
||||
html.append("<td style='border:1px solid #333;text-align:left;padding-left:8px;'>").append(escapeHtml(projectName)).append("</td>");
|
||||
html.append("</tr>");
|
||||
|
||||
html.append("<tr>");
|
||||
html.append("<th style='border:1px solid #333;background-color:#E8E8E8;text-align:center;font-weight:bold;'>구매유형</th>");
|
||||
html.append("<td style='border:1px solid #333;text-align:center;'>").append(escapeHtml(purchaseTypeName)).append("</td>");
|
||||
html.append("<th style='border:1px solid #333;background-color:#E8E8E8;text-align:center;font-weight:bold;'>주문유형</th>");
|
||||
html.append("<td style='border:1px solid #333;text-align:center;'>").append(escapeHtml(orderTypeName)).append("</td>");
|
||||
html.append("</tr>");
|
||||
|
||||
html.append("<tr>");
|
||||
html.append("<th style='border:1px solid #333;background-color:#E8E8E8;text-align:center;font-weight:bold;'>제품구분</th>");
|
||||
html.append("<td style='border:1px solid #333;text-align:center;'>").append(escapeHtml(productName)).append("</td>");
|
||||
html.append("<th style='border:1px solid #333;background-color:#E8E8E8;text-align:center;font-weight:bold;'>고객사</th>");
|
||||
html.append("<td style='border:1px solid #333;text-align:left;padding-left:8px;'>").append(escapeHtml(customerName)).append("</td>");
|
||||
html.append("</tr>");
|
||||
|
||||
html.append("<tr>");
|
||||
html.append("<th style='border:1px solid #333;background-color:#E8E8E8;text-align:center;font-weight:bold;'>작성자</th>");
|
||||
html.append("<td style='border:1px solid #333;text-align:center;'>").append(escapeHtml(writerName)).append("</td>");
|
||||
if(!totalAmount.isEmpty()){
|
||||
html.append("<th style='border:1px solid #333;background-color:#E8E8E8;text-align:center;font-weight:bold;'>합계금액</th>");
|
||||
html.append("<td style='border:1px solid #333;text-align:right;padding-right:8px;'>").append(escapeHtml(totalAmount)).append("</td>");
|
||||
} else {
|
||||
html.append("<td style='border:1px solid #333;' colspan='2'></td>");
|
||||
}
|
||||
html.append("</tr>");
|
||||
|
||||
html.append("</tbody></table>");
|
||||
|
||||
// 품의서 품목 리스트 조회
|
||||
try {
|
||||
Map<String, Object> partParam = new HashMap();
|
||||
partParam.put("PROPOSAL_OBJID", targetObjId);
|
||||
List<Map> partList = sqlSession.selectList("salesMng.getProposalPartList", partParam);
|
||||
|
||||
if(partList != null && !partList.isEmpty()){
|
||||
html.append("<br/>");
|
||||
html.append("<table cellspacing='0' cellpadding='4' style='border-collapse:collapse;width:100%;'>");
|
||||
html.append("<thead><tr>");
|
||||
html.append("<th style='border:1px solid #333;background-color:#4472C4;color:#fff;text-align:center;font-size:9pt;'>No</th>");
|
||||
html.append("<th style='border:1px solid #333;background-color:#4472C4;color:#fff;text-align:center;font-size:9pt;'>품번</th>");
|
||||
html.append("<th style='border:1px solid #333;background-color:#4472C4;color:#fff;text-align:center;font-size:9pt;'>품명</th>");
|
||||
html.append("<th style='border:1px solid #333;background-color:#4472C4;color:#fff;text-align:center;font-size:9pt;'>규격</th>");
|
||||
html.append("<th style='border:1px solid #333;background-color:#4472C4;color:#fff;text-align:center;font-size:9pt;'>수량</th>");
|
||||
html.append("<th style='border:1px solid #333;background-color:#4472C4;color:#fff;text-align:center;font-size:9pt;'>단가</th>");
|
||||
html.append("<th style='border:1px solid #333;background-color:#4472C4;color:#fff;text-align:center;font-size:9pt;'>금액</th>");
|
||||
html.append("<th style='border:1px solid #333;background-color:#4472C4;color:#fff;text-align:center;font-size:9pt;'>거래처</th>");
|
||||
html.append("</tr></thead><tbody>");
|
||||
|
||||
int idx = 1;
|
||||
for(Map partInfo : partList){
|
||||
html.append("<tr>");
|
||||
html.append("<td style='border:1px solid #ccc;text-align:center;font-size:9pt;'>").append(idx++).append("</td>");
|
||||
html.append("<td style='border:1px solid #ccc;text-align:left;padding-left:4px;font-size:9pt;'>").append(escapeHtml(CommonUtils.checkNull(partInfo.get("PART_NO")))).append("</td>");
|
||||
html.append("<td style='border:1px solid #ccc;text-align:left;padding-left:4px;font-size:9pt;'>").append(escapeHtml(CommonUtils.checkNull(partInfo.get("PART_NAME")))).append("</td>");
|
||||
html.append("<td style='border:1px solid #ccc;text-align:left;padding-left:4px;font-size:9pt;'>").append(escapeHtml(CommonUtils.checkNull(partInfo.get("SPEC")))).append("</td>");
|
||||
html.append("<td style='border:1px solid #ccc;text-align:right;padding-right:4px;font-size:9pt;'>").append(escapeHtml(CommonUtils.checkNull(partInfo.get("QTY")))).append("</td>");
|
||||
html.append("<td style='border:1px solid #ccc;text-align:right;padding-right:4px;font-size:9pt;'>").append(escapeHtml(CommonUtils.checkNull(partInfo.get("UNIT_PRICE")))).append("</td>");
|
||||
html.append("<td style='border:1px solid #ccc;text-align:right;padding-right:4px;font-size:9pt;'>").append(escapeHtml(CommonUtils.checkNull(partInfo.get("TOTAL_PRICE")))).append("</td>");
|
||||
html.append("<td style='border:1px solid #ccc;text-align:left;padding-left:4px;font-size:9pt;'>").append(escapeHtml(CommonUtils.checkNull(partInfo.get("VENDOR_NAME")))).append("</td>");
|
||||
html.append("</tr>");
|
||||
}
|
||||
html.append("</tbody></table>");
|
||||
}
|
||||
} catch(Exception e){
|
||||
System.err.println("품의서 품목 리스트 조회 오류: " + e.getMessage());
|
||||
}
|
||||
|
||||
// 비고
|
||||
if(!remark.isEmpty()){
|
||||
html.append("<br/><p><strong>비고:</strong> ").append(escapeHtml(remark)).append("</p>");
|
||||
}
|
||||
|
||||
html.append("</div>");
|
||||
return html.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML 이스케이프 (XSS 방지)
|
||||
*/
|
||||
private String escapeHtml(String value){
|
||||
if(value == null || value.isEmpty()) return "";
|
||||
return value.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user