V20260210 #146
@@ -1061,10 +1061,11 @@ function fn_createQuotationRequest() {
|
||||
checkedRows.forEach(function(item) {
|
||||
var vendorPm = item.VENDOR_PM || '';
|
||||
var processingVendor = item.PROCESSING_VENDOR || '';
|
||||
var originalObjids = item.ORIGINAL_OBJIDS || item.OBJID;
|
||||
|
||||
if(vendorPm && vendorPm !== '') {
|
||||
supplyItems.push({
|
||||
objid: item.OBJID,
|
||||
originalObjids: originalObjids,
|
||||
vendorObjid: vendorPm,
|
||||
partNo: item.PART_NO,
|
||||
partName: item.PART_NAME
|
||||
@@ -1073,7 +1074,7 @@ function fn_createQuotationRequest() {
|
||||
|
||||
if(processingVendor && processingVendor !== '') {
|
||||
processingItems.push({
|
||||
objid: item.OBJID,
|
||||
originalObjids: originalObjids,
|
||||
vendorObjid: processingVendor,
|
||||
partNo: item.PART_NO,
|
||||
partName: item.PART_NAME
|
||||
@@ -1098,14 +1099,14 @@ function fn_createQuotationRequest() {
|
||||
if(!supplyVendorGroups[item.vendorObjid]) {
|
||||
supplyVendorGroups[item.vendorObjid] = [];
|
||||
}
|
||||
supplyVendorGroups[item.vendorObjid].push(item.objid);
|
||||
supplyVendorGroups[item.vendorObjid].push(item.originalObjids);
|
||||
});
|
||||
|
||||
processingItems.forEach(function(item) {
|
||||
if(!processingVendorGroups[item.vendorObjid]) {
|
||||
processingVendorGroups[item.vendorObjid] = [];
|
||||
}
|
||||
processingVendorGroups[item.vendorObjid].push(item.objid);
|
||||
processingVendorGroups[item.vendorObjid].push(item.originalObjids);
|
||||
});
|
||||
|
||||
// 생성할 견적요청서 목록 표시
|
||||
|
||||
@@ -893,14 +893,12 @@ function fn_processQuotationRequestCreation(salesRequestObjid, purchaseList) {
|
||||
// 대소문자 모두 처리 (서버에서 소문자로 반환될 수 있음)
|
||||
var vendorPm = fnc_checkNull(item.VENDOR_PM || item.vendor_pm);
|
||||
var processingVendor = fnc_checkNull(item.PROCESSING_VENDOR || item.processing_vendor);
|
||||
var objid = fnc_checkNull(item.OBJID || item.objid);
|
||||
var originalObjids = fnc_checkNull(item.ORIGINAL_OBJIDS || item.original_objids || item.OBJID || item.objid);
|
||||
var vendorName = fnc_checkNull(item.VENDOR_NAME || item.vendor_name);
|
||||
var processingVendorName = fnc_checkNull(item.PROCESSING_VENDOR_NAME || item.processing_vendor_name);
|
||||
// 견적요청서 생성 가능 여부 플래그
|
||||
var canCreateSupply = fnc_checkNull(item.CAN_CREATE_SUPPLY || item.can_create_supply);
|
||||
var canCreateProcessing = fnc_checkNull(item.CAN_CREATE_PROCESSING || item.can_create_processing);
|
||||
|
||||
// 공급업체 견적요청서 생성 가능한 경우
|
||||
if(vendorPm !== '' && canCreateSupply === 'Y') {
|
||||
if(!supplyVendorGroups[vendorPm]) {
|
||||
supplyVendorGroups[vendorPm] = {
|
||||
@@ -908,10 +906,9 @@ function fn_processQuotationRequestCreation(salesRequestObjid, purchaseList) {
|
||||
parts: []
|
||||
};
|
||||
}
|
||||
supplyVendorGroups[vendorPm].parts.push(objid);
|
||||
supplyVendorGroups[vendorPm].parts.push(originalObjids);
|
||||
}
|
||||
|
||||
// 가공업체 견적요청서 생성 가능한 경우
|
||||
if(processingVendor !== '' && canCreateProcessing === 'Y') {
|
||||
if(!processingVendorGroups[processingVendor]) {
|
||||
processingVendorGroups[processingVendor] = {
|
||||
@@ -919,7 +916,7 @@ function fn_processQuotationRequestCreation(salesRequestObjid, purchaseList) {
|
||||
parts: []
|
||||
};
|
||||
}
|
||||
processingVendorGroups[processingVendor].parts.push(objid);
|
||||
processingVendorGroups[processingVendor].parts.push(originalObjids);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
500
src/com/pms/api/SalesSlipApiClient.java
Normal file
500
src/com/pms/api/SalesSlipApiClient.java
Normal file
@@ -0,0 +1,500 @@
|
||||
package com.pms.api;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.security.cert.X509Certificate;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import java.util.Random;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 아마란스10 자동전표데이터등록 API 클라이언트
|
||||
* 매출마감 시 회계전표를 ERP에 자동 등록한다.
|
||||
* API: /apiproxy/api11A10
|
||||
*/
|
||||
public class SalesSlipApiClient {
|
||||
|
||||
private static final String API_URL = "~/apiproxy/api11A10";
|
||||
private static final String CALLER_NAME = "API_gcmsAmaranth40578";
|
||||
private static final String ACCESS_TOKEN = "MN5KzKBWRAa92BPxDlRLl3GcsxeZXc";
|
||||
private static final String HASH_KEY = "22519103205540290721741689643674301018832465";
|
||||
private static final String GROUP_SEQ = "gcmsAmaranth40578";
|
||||
|
||||
// 회사코드 / 회계단위
|
||||
public static final String CO_CD = "1000";
|
||||
public static final String DIV_CD = "1000";
|
||||
|
||||
// 김하얀 사원코드 (전표 작성자)
|
||||
public static final String INSERT_ID = "2024010";
|
||||
|
||||
// 영업팀 부서코드
|
||||
public static final String SALES_DEPT_CD = "004";
|
||||
|
||||
// 계정과목 코드 (기본값, DB erp_acct_code에서 조회하여 덮어쓸 수 있음)
|
||||
public static final String DEFAULT_ACCT_ACCOUNTS_RECEIVABLE = "1080000"; // 외상매출금
|
||||
public static final String DEFAULT_ACCT_VAT_COLLECTED = "2550000"; // 부가세예수금
|
||||
public static final String DEFAULT_ACCT_PRODUCT_SALES = "4040000"; // 제품매출
|
||||
|
||||
// 실제 사용할 계정코드 (Service에서 DB 조회 후 설정)
|
||||
private String acctAccountsReceivable = DEFAULT_ACCT_ACCOUNTS_RECEIVABLE;
|
||||
private String acctVatCollected = DEFAULT_ACCT_VAT_COLLECTED;
|
||||
private String acctProductSales = DEFAULT_ACCT_PRODUCT_SALES;
|
||||
|
||||
/**
|
||||
* DB에서 조회한 계정과목 코드를 설정한다.
|
||||
* erp_acct_code 테이블에서 api11A02로 동기화된 값을 사용.
|
||||
*/
|
||||
public void setAccountCodes(String accountsReceivable, String vatCollected, String productSales) {
|
||||
if (accountsReceivable != null && !accountsReceivable.isEmpty()) {
|
||||
this.acctAccountsReceivable = accountsReceivable;
|
||||
}
|
||||
if (vatCollected != null && !vatCollected.isEmpty()) {
|
||||
this.acctVatCollected = vatCollected;
|
||||
}
|
||||
if (productSales != null && !productSales.isEmpty()) {
|
||||
this.acctProductSales = productSales;
|
||||
}
|
||||
System.out.println("[SalesSlipApi] 계정과목 설정 - 외상매출금: " + this.acctAccountsReceivable
|
||||
+ ", 부가세예수금: " + this.acctVatCollected
|
||||
+ ", 제품매출: " + this.acctProductSales);
|
||||
}
|
||||
|
||||
// 증빙코드
|
||||
public static final String ATTR_TAX_INVOICE = "1"; // 세금계산서
|
||||
public static final String ATTR_ETC = "9"; // 기타
|
||||
|
||||
// 세무구분
|
||||
public static final String TAX_FG_DOMESTIC = "11"; // 과세-세금계산서
|
||||
public static final String TAX_FG_EXPORT = "12"; // 영세-수출
|
||||
|
||||
// 전표유형
|
||||
public static final String DOCU_TY_SALES = "3"; // 매출
|
||||
|
||||
// 차대구분
|
||||
public static final String DRCR_DEBIT = "3"; // 차변
|
||||
public static final String DRCR_CREDIT = "4"; // 대변
|
||||
|
||||
/**
|
||||
* 자동전표 데이터를 아마란스에 등록한다.
|
||||
*
|
||||
* @param baseUrl API 서버 기본 URL (https://erp.rps-korea.com)
|
||||
* @param requestBody 전표 데이터 JSON 문자열
|
||||
* @return API 응답 JSON 문자열
|
||||
* @throws Exception API 호출 실패
|
||||
*/
|
||||
public String registerSalesSlip(String baseUrl, String requestBody) throws Exception {
|
||||
if (requestBody == null || requestBody.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("전표 데이터(requestBody)는 필수입니다.");
|
||||
}
|
||||
|
||||
System.setProperty("https.protocols", "TLSv1.2");
|
||||
|
||||
TrustManager[] trustAllCerts = new TrustManager[] {
|
||||
new X509TrustManager() {
|
||||
public X509Certificate[] getAcceptedIssuers() { return null; }
|
||||
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
|
||||
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
|
||||
}
|
||||
};
|
||||
|
||||
SSLContext sc = SSLContext.getInstance("TLS");
|
||||
sc.init(null, trustAllCerts, new java.security.SecureRandom());
|
||||
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
|
||||
HttpsURLConnection.setDefaultHostnameVerifier(new javax.net.ssl.HostnameVerifier() {
|
||||
public boolean verify(String hostname, javax.net.ssl.SSLSession session) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
String urlPath = API_URL.replace("~", "");
|
||||
String cleanBaseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
|
||||
String fullUrl = cleanBaseUrl + urlPath;
|
||||
|
||||
System.out.println("[SalesSlipApi] 요청 URL: " + fullUrl);
|
||||
System.out.println("[SalesSlipApi] 요청 Body: " + requestBody);
|
||||
|
||||
URL url = new URL(fullUrl);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
|
||||
connection.setConnectTimeout(30000);
|
||||
connection.setReadTimeout(30000);
|
||||
connection.setInstanceFollowRedirects(false);
|
||||
|
||||
try {
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
|
||||
connection.setRequestProperty("Accept", "application/json");
|
||||
|
||||
connection.setRequestProperty("callerName", CALLER_NAME);
|
||||
connection.setRequestProperty("Authorization", "Bearer " + ACCESS_TOKEN);
|
||||
|
||||
String transactionId = generateTransactionId();
|
||||
connection.setRequestProperty("transaction-id", transactionId);
|
||||
|
||||
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
|
||||
connection.setRequestProperty("timestamp", timestamp);
|
||||
|
||||
connection.setRequestProperty("groupSeq", GROUP_SEQ);
|
||||
|
||||
String wehagoSign = generateWehagoSign(ACCESS_TOKEN, transactionId, timestamp, urlPath);
|
||||
connection.setRequestProperty("wehago-sign", wehagoSign);
|
||||
|
||||
connection.setDoOutput(true);
|
||||
connection.setDoInput(true);
|
||||
|
||||
OutputStreamWriter writer = new OutputStreamWriter(
|
||||
connection.getOutputStream(), StandardCharsets.UTF_8);
|
||||
writer.write(requestBody);
|
||||
writer.flush();
|
||||
writer.close();
|
||||
|
||||
int responseCode = connection.getResponseCode();
|
||||
|
||||
// 리다이렉트 처리
|
||||
if (responseCode == 301 || responseCode == 302 || responseCode == 303 ||
|
||||
responseCode == 307 || responseCode == 308) {
|
||||
String location = connection.getHeaderField("Location");
|
||||
connection.disconnect();
|
||||
|
||||
if (location != null) {
|
||||
URL redirectUrl = new URL(location);
|
||||
connection = (HttpURLConnection) redirectUrl.openConnection();
|
||||
connection.setConnectTimeout(30000);
|
||||
connection.setReadTimeout(30000);
|
||||
connection.setInstanceFollowRedirects(false);
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
|
||||
connection.setRequestProperty("Accept", "application/json");
|
||||
connection.setRequestProperty("callerName", CALLER_NAME);
|
||||
connection.setRequestProperty("Authorization", "Bearer " + ACCESS_TOKEN);
|
||||
connection.setRequestProperty("transaction-id", transactionId);
|
||||
connection.setRequestProperty("timestamp", timestamp);
|
||||
connection.setRequestProperty("groupSeq", GROUP_SEQ);
|
||||
connection.setRequestProperty("wehago-sign", wehagoSign);
|
||||
|
||||
connection.setDoOutput(true);
|
||||
connection.setDoInput(true);
|
||||
|
||||
OutputStreamWriter redirectWriter = new OutputStreamWriter(
|
||||
connection.getOutputStream(), StandardCharsets.UTF_8);
|
||||
redirectWriter.write(requestBody);
|
||||
redirectWriter.flush();
|
||||
redirectWriter.close();
|
||||
|
||||
responseCode = connection.getResponseCode();
|
||||
}
|
||||
}
|
||||
|
||||
BufferedReader reader = null;
|
||||
StringBuilder response = new StringBuilder();
|
||||
|
||||
try {
|
||||
if (responseCode >= 200 && responseCode < 300) {
|
||||
reader = new BufferedReader(new InputStreamReader(
|
||||
connection.getInputStream(), StandardCharsets.UTF_8));
|
||||
} else {
|
||||
java.io.InputStream errorStream = connection.getErrorStream();
|
||||
if (errorStream != null) {
|
||||
reader = new BufferedReader(new InputStreamReader(
|
||||
errorStream, StandardCharsets.UTF_8));
|
||||
} else {
|
||||
throw new Exception("전표등록 API 호출 실패: HTTP " + responseCode);
|
||||
}
|
||||
}
|
||||
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
response.append(line);
|
||||
}
|
||||
} finally {
|
||||
if (reader != null) {
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
|
||||
String responseStr = response.toString();
|
||||
System.out.println("[SalesSlipApi] 응답 코드: " + responseCode);
|
||||
System.out.println("[SalesSlipApi] 응답 Body: " + responseStr);
|
||||
|
||||
if (responseCode >= 200 && responseCode < 300) {
|
||||
return responseStr;
|
||||
} else {
|
||||
throw new Exception("전표등록 API 호출 실패: HTTP " + responseCode + " - " + responseStr);
|
||||
}
|
||||
|
||||
} finally {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 국내 매출전표 JSON 생성 (세금계산서)
|
||||
*
|
||||
* @param menuDt 작성일자 (yyyyMMdd, 세금계산서발행일)
|
||||
* @param menuSq 작성번호 (동일 일자 내 고유)
|
||||
* @param slipTitle 전표제목 (거래처명_품명)
|
||||
* @param trCd 거래처코드 (client_mng.client_cd)
|
||||
* @param trNm 거래처명
|
||||
* @param totalAmount 총액 (공급가액 + 부가세)
|
||||
* @param vatAmount 부가세
|
||||
* @param supplyAmount 공급가액
|
||||
* @param itemSummary 적요 (품명)
|
||||
* @param taxFg 세무구분코드 (convertTaxType으로 변환한 값)
|
||||
* @return 전표 JSON 문자열
|
||||
*/
|
||||
public String buildDomesticSlipJson(String menuDt, int menuSq, String slipTitle,
|
||||
String trCd, String trNm,
|
||||
long totalAmount, long vatAmount, long supplyAmount,
|
||||
String itemSummary, String taxFg) {
|
||||
StringBuilder json = new StringBuilder();
|
||||
json.append("{");
|
||||
json.append("\"coCd\":\"").append(CO_CD).append("\"");
|
||||
json.append(",\"groupSeq\":\"").append(GROUP_SEQ).append("\"");
|
||||
json.append(",\"data\":[");
|
||||
|
||||
// Line 1: 차변 외상매출금 (총액)
|
||||
json.append("{");
|
||||
json.append("\"inDivCd\":\"").append(DIV_CD).append("\"");
|
||||
json.append(",\"menuDt\":\"").append(escapeJson(menuDt)).append("\"");
|
||||
json.append(",\"menuSq\":").append(menuSq);
|
||||
json.append(",\"menuLnSq\":1");
|
||||
json.append(",\"isuDoc\":\"").append(escapeJson(slipTitle)).append("\"");
|
||||
json.append(",\"docuTy\":\"").append(DOCU_TY_SALES).append("\"");
|
||||
json.append(",\"drcrFg\":\"").append(DRCR_DEBIT).append("\"");
|
||||
json.append(",\"acctCd\":\"").append(acctAccountsReceivable).append("\"");
|
||||
json.append(",\"trCd\":\"").append(escapeJson(trCd)).append("\"");
|
||||
json.append(",\"trNm\":\"").append(escapeJson(trNm)).append("\"");
|
||||
json.append(",\"acctAm\":").append(totalAmount);
|
||||
json.append(",\"attrCd\":\"").append(ATTR_TAX_INVOICE).append("\"");
|
||||
json.append(",\"rmkDc\":\"").append(escapeJson(itemSummary)).append("\"");
|
||||
json.append(",\"ctDept\":\"").append(SALES_DEPT_CD).append("\"");
|
||||
json.append(",\"insertId\":\"").append(INSERT_ID).append("\"");
|
||||
json.append(",\"exFg\":\"1\"");
|
||||
json.append("}");
|
||||
|
||||
// Line 2: 대변 부가세예수금 (부가세)
|
||||
json.append(",{");
|
||||
json.append("\"inDivCd\":\"").append(DIV_CD).append("\"");
|
||||
json.append(",\"menuDt\":\"").append(escapeJson(menuDt)).append("\"");
|
||||
json.append(",\"menuSq\":").append(menuSq);
|
||||
json.append(",\"menuLnSq\":2");
|
||||
json.append(",\"isuDoc\":\"").append(escapeJson(slipTitle)).append("\"");
|
||||
json.append(",\"docuTy\":\"").append(DOCU_TY_SALES).append("\"");
|
||||
json.append(",\"drcrFg\":\"").append(DRCR_CREDIT).append("\"");
|
||||
json.append(",\"acctCd\":\"").append(acctVatCollected).append("\"");
|
||||
json.append(",\"trCd\":\"").append(escapeJson(trCd)).append("\"");
|
||||
json.append(",\"trNm\":\"").append(escapeJson(trNm)).append("\"");
|
||||
json.append(",\"acctAm\":").append(vatAmount);
|
||||
json.append(",\"attrCd\":\"").append(ATTR_TAX_INVOICE).append("\"");
|
||||
json.append(",\"rmkDc\":\"").append(escapeJson(itemSummary)).append("\"");
|
||||
json.append(",\"taxFg\":\"").append(escapeJson(taxFg)).append("\"");
|
||||
json.append(",\"jeonjaYn\":\"1\"");
|
||||
json.append(",\"supAm\":").append(supplyAmount);
|
||||
json.append(",\"ctDept\":\"").append(SALES_DEPT_CD).append("\"");
|
||||
json.append(",\"insertId\":\"").append(INSERT_ID).append("\"");
|
||||
json.append(",\"exFg\":\"1\"");
|
||||
json.append("}");
|
||||
|
||||
// Line 3: 대변 제품매출 (공급가액)
|
||||
json.append(",{");
|
||||
json.append("\"inDivCd\":\"").append(DIV_CD).append("\"");
|
||||
json.append(",\"menuDt\":\"").append(escapeJson(menuDt)).append("\"");
|
||||
json.append(",\"menuSq\":").append(menuSq);
|
||||
json.append(",\"menuLnSq\":3");
|
||||
json.append(",\"isuDoc\":\"").append(escapeJson(slipTitle)).append("\"");
|
||||
json.append(",\"docuTy\":\"").append(DOCU_TY_SALES).append("\"");
|
||||
json.append(",\"drcrFg\":\"").append(DRCR_CREDIT).append("\"");
|
||||
json.append(",\"acctCd\":\"").append(acctProductSales).append("\"");
|
||||
json.append(",\"trCd\":\"").append(escapeJson(trCd)).append("\"");
|
||||
json.append(",\"trNm\":\"").append(escapeJson(trNm)).append("\"");
|
||||
json.append(",\"acctAm\":").append(supplyAmount);
|
||||
json.append(",\"attrCd\":\"").append(ATTR_TAX_INVOICE).append("\"");
|
||||
json.append(",\"rmkDc\":\"").append(escapeJson(itemSummary)).append("\"");
|
||||
json.append(",\"ctDept\":\"").append(SALES_DEPT_CD).append("\"");
|
||||
json.append(",\"insertId\":\"").append(INSERT_ID).append("\"");
|
||||
json.append(",\"exFg\":\"1\"");
|
||||
json.append("}");
|
||||
|
||||
json.append("]}");
|
||||
return json.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 해외 매출전표 JSON 생성 (수출)
|
||||
*
|
||||
* @param menuDt 작성일자 (yyyyMMdd, 선적일자)
|
||||
* @param menuSq 작성번호
|
||||
* @param slipTitle 전표제목 (거래처명_품명_외화총액_환율)
|
||||
* @param trCd 거래처코드
|
||||
* @param trNm 거래처명
|
||||
* @param krwTotalAmount 원화총액
|
||||
* @param exchCd 환종코드 (USD, JPY 등)
|
||||
* @param exchangeRate 환율
|
||||
* @param foreignAmount 외화금액
|
||||
* @param exportDeclNo 수출신고필증 신고번호
|
||||
* @param loadingDate 선적일자 (yyyyMMdd)
|
||||
* @param itemSummary 적요
|
||||
* @return 전표 JSON 문자열
|
||||
*/
|
||||
public String buildOverseasSlipJson(String menuDt, int menuSq, String slipTitle,
|
||||
String trCd, String trNm,
|
||||
long krwTotalAmount,
|
||||
String exchCd, double exchangeRate, double foreignAmount,
|
||||
String exportDeclNo, String loadingDate,
|
||||
String itemSummary, String taxFg) {
|
||||
StringBuilder json = new StringBuilder();
|
||||
json.append("{");
|
||||
json.append("\"coCd\":\"").append(CO_CD).append("\"");
|
||||
json.append(",\"groupSeq\":\"").append(GROUP_SEQ).append("\"");
|
||||
json.append(",\"data\":[");
|
||||
|
||||
// Line 1: 차변 외상매출금 (원화총액)
|
||||
json.append("{");
|
||||
json.append("\"inDivCd\":\"").append(DIV_CD).append("\"");
|
||||
json.append(",\"menuDt\":\"").append(escapeJson(menuDt)).append("\"");
|
||||
json.append(",\"menuSq\":").append(menuSq);
|
||||
json.append(",\"menuLnSq\":1");
|
||||
json.append(",\"isuDoc\":\"").append(escapeJson(slipTitle)).append("\"");
|
||||
json.append(",\"docuTy\":\"").append(DOCU_TY_SALES).append("\"");
|
||||
json.append(",\"drcrFg\":\"").append(DRCR_DEBIT).append("\"");
|
||||
json.append(",\"acctCd\":\"").append(acctAccountsReceivable).append("\"");
|
||||
json.append(",\"trCd\":\"").append(escapeJson(trCd)).append("\"");
|
||||
json.append(",\"trNm\":\"").append(escapeJson(trNm)).append("\"");
|
||||
json.append(",\"acctAm\":").append(krwTotalAmount);
|
||||
json.append(",\"attrCd\":\"").append(ATTR_ETC).append("\"");
|
||||
json.append(",\"rmkDc\":\"").append(escapeJson(itemSummary)).append("\"");
|
||||
json.append(",\"ctDept\":\"").append(SALES_DEPT_CD).append("\"");
|
||||
json.append(",\"insertId\":\"").append(INSERT_ID).append("\"");
|
||||
json.append(",\"exFg\":\"1\"");
|
||||
json.append("}");
|
||||
|
||||
// Line 2: 대변 제품매출 (원화총액) — 해외는 제품매출이 2번
|
||||
json.append(",{");
|
||||
json.append("\"inDivCd\":\"").append(DIV_CD).append("\"");
|
||||
json.append(",\"menuDt\":\"").append(escapeJson(menuDt)).append("\"");
|
||||
json.append(",\"menuSq\":").append(menuSq);
|
||||
json.append(",\"menuLnSq\":2");
|
||||
json.append(",\"isuDoc\":\"").append(escapeJson(slipTitle)).append("\"");
|
||||
json.append(",\"docuTy\":\"").append(DOCU_TY_SALES).append("\"");
|
||||
json.append(",\"drcrFg\":\"").append(DRCR_CREDIT).append("\"");
|
||||
json.append(",\"acctCd\":\"").append(acctProductSales).append("\"");
|
||||
json.append(",\"trCd\":\"").append(escapeJson(trCd)).append("\"");
|
||||
json.append(",\"trNm\":\"").append(escapeJson(trNm)).append("\"");
|
||||
json.append(",\"acctAm\":").append(krwTotalAmount);
|
||||
json.append(",\"attrCd\":\"").append(ATTR_ETC).append("\"");
|
||||
json.append(",\"rmkDc\":\"").append(escapeJson(itemSummary)).append("\"");
|
||||
json.append(",\"ctDept\":\"").append(SALES_DEPT_CD).append("\"");
|
||||
json.append(",\"insertId\":\"").append(INSERT_ID).append("\"");
|
||||
json.append(",\"exFg\":\"1\"");
|
||||
json.append("}");
|
||||
|
||||
// Line 3: 대변 부가세예수금 (0원) + 수출 부가세 정보
|
||||
json.append(",{");
|
||||
json.append("\"inDivCd\":\"").append(DIV_CD).append("\"");
|
||||
json.append(",\"menuDt\":\"").append(escapeJson(menuDt)).append("\"");
|
||||
json.append(",\"menuSq\":").append(menuSq);
|
||||
json.append(",\"menuLnSq\":3");
|
||||
json.append(",\"isuDoc\":\"").append(escapeJson(slipTitle)).append("\"");
|
||||
json.append(",\"docuTy\":\"").append(DOCU_TY_SALES).append("\"");
|
||||
json.append(",\"drcrFg\":\"").append(DRCR_CREDIT).append("\"");
|
||||
json.append(",\"acctCd\":\"").append(acctVatCollected).append("\"");
|
||||
json.append(",\"trCd\":\"").append(escapeJson(trCd)).append("\"");
|
||||
json.append(",\"trNm\":\"").append(escapeJson(trNm)).append("\"");
|
||||
json.append(",\"acctAm\":0");
|
||||
json.append(",\"attrCd\":\"").append(ATTR_ETC).append("\"");
|
||||
json.append(",\"rmkDc\":\"").append(escapeJson(itemSummary)).append("\"");
|
||||
// 부가세 수출 관련 필드
|
||||
json.append(",\"taxFg\":\"").append(escapeJson(taxFg)).append("\"");
|
||||
json.append(",\"issDt\":\"").append(escapeJson(loadingDate)).append("\"");
|
||||
json.append(",\"dummy1\":\"").append(escapeJson(exchCd)).append("\"");
|
||||
json.append(",\"billAm\":").append(exchangeRate);
|
||||
json.append(",\"cashAm\":").append(foreignAmount);
|
||||
json.append(",\"supAm\":").append(krwTotalAmount);
|
||||
json.append(",\"ctNb\":\"").append(escapeJson(exportDeclNo)).append("\"");
|
||||
json.append(",\"ctDept\":\"").append(SALES_DEPT_CD).append("\"");
|
||||
json.append(",\"insertId\":\"").append(INSERT_ID).append("\"");
|
||||
json.append(",\"exFg\":\"1\"");
|
||||
json.append("}");
|
||||
|
||||
json.append("]}");
|
||||
return json.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* PLM 환종 code_id → 아마란스 ISO 환종코드 변환
|
||||
*/
|
||||
/**
|
||||
* PLM 과세구분 code_id → 아마란스 세무구분(taxFg) 변환
|
||||
* 부가세 라인에만 세무구분이 들어감
|
||||
*/
|
||||
public static String convertTaxType(String plmTaxTypeCodeId) {
|
||||
if (plmTaxTypeCodeId == null) return "";
|
||||
switch (plmTaxTypeCodeId) {
|
||||
case "0900216": return "11"; // 과세매출 → 과세-세금계산서
|
||||
case "0900217": return "12"; // 수출 → 영세-수출
|
||||
case "0900218": return "51"; // 과세매입 → 과세-세금계산서(매입)
|
||||
case "0900219": return "52"; // 영세매입 → 영세-수출(매입)
|
||||
case "0900220": return "54"; // 수입
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static String convertCurrencyCode(String plmCurrencyCodeId) {
|
||||
if (plmCurrencyCodeId == null) return "KRW";
|
||||
switch (plmCurrencyCodeId) {
|
||||
case "0001566": return "KRW";
|
||||
case "0001534": return "USD";
|
||||
case "0001537": return "JPY";
|
||||
case "0001536": return "CNY";
|
||||
case "0001535": return "EUR";
|
||||
default: return "KRW";
|
||||
}
|
||||
}
|
||||
|
||||
private String escapeJson(String value) {
|
||||
if (value == null) return "";
|
||||
return value.replace("\\", "\\\\")
|
||||
.replace("\"", "\\\"")
|
||||
.replace("\n", "\\n")
|
||||
.replace("\r", "\\r")
|
||||
.replace("\t", "\\t");
|
||||
}
|
||||
|
||||
private String generateTransactionId() {
|
||||
String chars = "0123456789abcdef";
|
||||
Random random = new Random();
|
||||
StringBuilder sb = new StringBuilder(32);
|
||||
for (int i = 0; i < 32; i++) {
|
||||
sb.append(chars.charAt(random.nextInt(chars.length())));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String generateWehagoSign(String accessToken, String transactionId,
|
||||
String timestamp, String urlPath) throws Exception {
|
||||
try {
|
||||
String value = accessToken + transactionId + timestamp + urlPath;
|
||||
SecretKeySpec keySpec = new SecretKeySpec(
|
||||
HASH_KEY.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(keySpec);
|
||||
byte[] encrypted = mac.doFinal(value.getBytes(StandardCharsets.UTF_8));
|
||||
return Base64.encodeBase64String(encrypted);
|
||||
} catch (Exception e) {
|
||||
System.err.println("[SalesSlipApi] Wehago-sign 생성 오류: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4956,6 +4956,35 @@ ORDER BY V.PATH2
|
||||
WHERE MD.OBJID = #{OBJID}
|
||||
</select>
|
||||
|
||||
<!-- 구매리스트 품목 정보 조회 (그룹핑된 OBJID 목록 합산) - 견적요청서 생성용 -->
|
||||
<select id="getGroupedSalesRequestPartInfo" parameterType="map" resultType="com.pms.common.UpperKeyMap">
|
||||
SELECT
|
||||
MIN(MD.OBJID) AS OBJID,
|
||||
MIN(MD.MBOM_HEADER_OBJID) AS MBOM_HEADER_OBJID,
|
||||
MD.PART_OBJID,
|
||||
MIN(PM.PART_NO) AS PART_NO,
|
||||
MIN(PM.PART_NAME) AS PART_NAME,
|
||||
COALESCE(MIN(MD.RAW_MATERIAL), '') AS RAW_MATERIAL,
|
||||
MIN(MD.RAW_MATERIAL_PART_NO) AS RAW_MATERIAL_NO,
|
||||
COALESCE(MIN(MD.RAW_MATERIAL_SIZE), '') AS SIZE,
|
||||
SUM(COALESCE(MD.PO_QTY, 0)) AS PO_QTY,
|
||||
SUM(COALESCE(MD.PRODUCTION_QTY, 0)) AS PRODUCTION_QTY,
|
||||
MIN(MD.UNIT_PRICE) AS UNIT_PRICE,
|
||||
MIN(MD.PROCESSING_UNIT_PRICE) AS PROCESSING_UNIT_PRICE,
|
||||
MIN(MD.VENDOR) AS VENDOR_PM,
|
||||
(SELECT CLIENT_NM FROM CLIENT_MNG WHERE OBJID::VARCHAR = MIN(MD.VENDOR)) AS VENDOR_NAME,
|
||||
MIN(MD.PROCESSING_VENDOR) AS PROCESSING_VENDOR,
|
||||
(SELECT CLIENT_NM FROM CLIENT_MNG WHERE OBJID::VARCHAR = MIN(MD.PROCESSING_VENDOR)) AS PROCESSING_VENDOR_NAME,
|
||||
STRING_AGG(MD.OBJID::VARCHAR, ',' ORDER BY MD.OBJID) AS ORIGINAL_OBJIDS
|
||||
FROM MBOM_DETAIL MD
|
||||
LEFT JOIN PART_MNG PM ON MD.PART_OBJID::VARCHAR = PM.OBJID::VARCHAR
|
||||
WHERE MD.OBJID::VARCHAR IN
|
||||
<foreach item="objid" collection="OBJID_LIST" open="(" separator="," close=")">
|
||||
#{objid}
|
||||
</foreach>
|
||||
GROUP BY MD.PART_OBJID
|
||||
</select>
|
||||
|
||||
<!-- =====================================================
|
||||
견적요청서 관리 쿼리
|
||||
===================================================== -->
|
||||
@@ -5301,71 +5330,89 @@ ORDER BY V.PATH2
|
||||
WHERE MBOM_HEADER_OBJID = #{MBOM_HEADER_OBJID}
|
||||
</update>
|
||||
|
||||
<!-- 구매리스트에서 견적요청서 생성 대상 조회 - M-BOM 기반 (MBOM_DETAIL에서 조회) -->
|
||||
<!-- 구매리스트에서 견적요청서 생성 대상 조회 - M-BOM 기반 (동일 품번 그룹핑) -->
|
||||
<!-- 공급업체/가공업체별로 견적요청서 생성 가능 여부 플래그 포함 -->
|
||||
<select id="getPurchaseListForQuotationFromMBom" parameterType="map" resultType="map">
|
||||
SELECT
|
||||
MD.OBJID,
|
||||
#{SALES_REQUEST_MASTER_OBJID} AS SALES_REQUEST_MASTER_OBJID,
|
||||
MD.PART_OBJID,
|
||||
PM.PART_NO,
|
||||
PM.PART_NAME,
|
||||
MD.RAW_MATERIAL,
|
||||
MD.RAW_MATERIAL_SIZE AS SIZE,
|
||||
MD.PO_QTY,
|
||||
MD.PRODUCTION_QTY,
|
||||
MD.UNIT_PRICE,
|
||||
MD.PROCESSING_UNIT_PRICE,
|
||||
MD.VENDOR AS VENDOR_PM,
|
||||
(SELECT CLIENT_NM FROM CLIENT_MNG WHERE OBJID::VARCHAR = MD.VENDOR) AS VENDOR_NAME,
|
||||
MD.PROCESSING_VENDOR,
|
||||
(SELECT CLIENT_NM FROM CLIENT_MNG WHERE OBJID::VARCHAR = MD.PROCESSING_VENDOR) AS PROCESSING_VENDOR_NAME,
|
||||
MD.RAW_MATERIAL_PART_NO AS RAW_MATERIAL_NO,
|
||||
'MBOM' AS DATA_SOURCE,
|
||||
-- 공급업체 견적요청서 생성 가능 여부
|
||||
CASE WHEN MD.VENDOR IS NOT NULL AND MD.VENDOR != '' AND NOT EXISTS (
|
||||
SELECT 1 FROM QUOTATION_REQUEST_DETAIL QRD
|
||||
INNER JOIN QUOTATION_REQUEST_MASTER QRM ON QRD.QUOTATION_REQUEST_MASTER_OBJID = QRM.OBJID
|
||||
WHERE QRD.SALES_REQUEST_PART_OBJID::VARCHAR = MD.OBJID::VARCHAR
|
||||
AND QRM.VENDOR_TYPE = 'SUPPLY'
|
||||
AND QRM.VENDOR_OBJID::VARCHAR = MD.VENDOR
|
||||
) THEN 'Y' ELSE 'N' END AS CAN_CREATE_SUPPLY,
|
||||
-- 가공업체 견적요청서 생성 가능 여부
|
||||
CASE WHEN MD.PROCESSING_VENDOR IS NOT NULL AND MD.PROCESSING_VENDOR != '' AND NOT EXISTS (
|
||||
SELECT 1 FROM QUOTATION_REQUEST_DETAIL QRD
|
||||
INNER JOIN QUOTATION_REQUEST_MASTER QRM ON QRD.QUOTATION_REQUEST_MASTER_OBJID = QRM.OBJID
|
||||
WHERE QRD.SALES_REQUEST_PART_OBJID::VARCHAR = MD.OBJID::VARCHAR
|
||||
AND QRM.VENDOR_TYPE = 'PROCESSING'
|
||||
AND QRM.VENDOR_OBJID::VARCHAR = MD.PROCESSING_VENDOR
|
||||
) THEN 'Y' ELSE 'N' END AS CAN_CREATE_PROCESSING
|
||||
FROM MBOM_DETAIL MD
|
||||
LEFT JOIN PART_MNG PM ON MD.PART_OBJID::VARCHAR = PM.OBJID::VARCHAR
|
||||
WHERE MD.MBOM_HEADER_OBJID = #{MBOM_HEADER_OBJID}
|
||||
AND (
|
||||
(MD.VENDOR IS NOT NULL AND MD.VENDOR != '')
|
||||
OR (MD.PROCESSING_VENDOR IS NOT NULL AND MD.PROCESSING_VENDOR != '')
|
||||
WITH GROUPED_MBOM AS (
|
||||
SELECT
|
||||
MIN(MD.OBJID) AS OBJID,
|
||||
STRING_AGG(MD.OBJID::VARCHAR, ',' ORDER BY MD.OBJID) AS ORIGINAL_OBJIDS,
|
||||
MD.PART_OBJID,
|
||||
MIN(PM.PART_NO) AS PART_NO,
|
||||
MIN(PM.PART_NAME) AS PART_NAME,
|
||||
COALESCE(MIN(MD.RAW_MATERIAL), '') AS RAW_MATERIAL,
|
||||
COALESCE(MIN(MD.RAW_MATERIAL_SIZE), '') AS SIZE,
|
||||
SUM(COALESCE(MD.PO_QTY, 0)) AS PO_QTY,
|
||||
SUM(COALESCE(MD.PRODUCTION_QTY, 0)) AS PRODUCTION_QTY,
|
||||
MIN(MD.UNIT_PRICE) AS UNIT_PRICE,
|
||||
MIN(MD.PROCESSING_UNIT_PRICE) AS PROCESSING_UNIT_PRICE,
|
||||
MIN(MD.VENDOR) AS VENDOR_PM,
|
||||
MIN(MD.PROCESSING_VENDOR) AS PROCESSING_VENDOR,
|
||||
MIN(MD.RAW_MATERIAL_PART_NO) AS RAW_MATERIAL_NO
|
||||
FROM MBOM_DETAIL MD
|
||||
LEFT JOIN PART_MNG PM ON MD.PART_OBJID::VARCHAR = PM.OBJID::VARCHAR
|
||||
WHERE MD.MBOM_HEADER_OBJID = #{MBOM_HEADER_OBJID}
|
||||
AND (
|
||||
(MD.VENDOR IS NOT NULL AND MD.VENDOR != '')
|
||||
OR (MD.PROCESSING_VENDOR IS NOT NULL AND MD.PROCESSING_VENDOR != '')
|
||||
)
|
||||
GROUP BY MD.PART_OBJID,
|
||||
COALESCE(MD.SUPPLY_TYPE, ''),
|
||||
COALESCE(MD.RAW_MATERIAL, ''),
|
||||
COALESCE(MD.RAW_MATERIAL_SIZE, '')
|
||||
)
|
||||
-- 공급업체 또는 가공업체 중 하나라도 견적요청서 생성 가능해야 함
|
||||
AND (
|
||||
-- 공급업체 견적요청서 생성 가능
|
||||
(MD.VENDOR IS NOT NULL AND MD.VENDOR != '' AND NOT EXISTS (
|
||||
SELECT
|
||||
G.OBJID,
|
||||
G.ORIGINAL_OBJIDS,
|
||||
#{SALES_REQUEST_MASTER_OBJID} AS SALES_REQUEST_MASTER_OBJID,
|
||||
G.PART_OBJID,
|
||||
G.PART_NO,
|
||||
G.PART_NAME,
|
||||
G.RAW_MATERIAL,
|
||||
G.SIZE,
|
||||
G.PO_QTY,
|
||||
G.PRODUCTION_QTY,
|
||||
G.UNIT_PRICE,
|
||||
G.PROCESSING_UNIT_PRICE,
|
||||
G.VENDOR_PM,
|
||||
(SELECT CLIENT_NM FROM CLIENT_MNG WHERE OBJID::VARCHAR = G.VENDOR_PM) AS VENDOR_NAME,
|
||||
G.PROCESSING_VENDOR,
|
||||
(SELECT CLIENT_NM FROM CLIENT_MNG WHERE OBJID::VARCHAR = G.PROCESSING_VENDOR) AS PROCESSING_VENDOR_NAME,
|
||||
G.RAW_MATERIAL_NO,
|
||||
'MBOM' AS DATA_SOURCE,
|
||||
CASE WHEN G.VENDOR_PM IS NOT NULL AND G.VENDOR_PM != '' AND NOT EXISTS (
|
||||
SELECT 1 FROM QUOTATION_REQUEST_DETAIL QRD
|
||||
INNER JOIN QUOTATION_REQUEST_MASTER QRM ON QRD.QUOTATION_REQUEST_MASTER_OBJID = QRM.OBJID
|
||||
WHERE QRD.SALES_REQUEST_PART_OBJID::VARCHAR = MD.OBJID::VARCHAR
|
||||
WHERE QRD.SALES_REQUEST_PART_OBJID = G.OBJID
|
||||
AND QRM.VENDOR_TYPE = 'SUPPLY'
|
||||
AND QRM.VENDOR_OBJID::VARCHAR = MD.VENDOR
|
||||
AND QRM.VENDOR_OBJID::VARCHAR = G.VENDOR_PM
|
||||
) THEN 'Y' ELSE 'N' END AS CAN_CREATE_SUPPLY,
|
||||
CASE WHEN G.PROCESSING_VENDOR IS NOT NULL AND G.PROCESSING_VENDOR != '' AND NOT EXISTS (
|
||||
SELECT 1 FROM QUOTATION_REQUEST_DETAIL QRD
|
||||
INNER JOIN QUOTATION_REQUEST_MASTER QRM ON QRD.QUOTATION_REQUEST_MASTER_OBJID = QRM.OBJID
|
||||
WHERE QRD.SALES_REQUEST_PART_OBJID = G.OBJID
|
||||
AND QRM.VENDOR_TYPE = 'PROCESSING'
|
||||
AND QRM.VENDOR_OBJID::VARCHAR = G.PROCESSING_VENDOR
|
||||
) THEN 'Y' ELSE 'N' END AS CAN_CREATE_PROCESSING
|
||||
FROM GROUPED_MBOM G
|
||||
WHERE (
|
||||
(G.VENDOR_PM IS NOT NULL AND G.VENDOR_PM != '' AND NOT EXISTS (
|
||||
SELECT 1 FROM QUOTATION_REQUEST_DETAIL QRD
|
||||
INNER JOIN QUOTATION_REQUEST_MASTER QRM ON QRD.QUOTATION_REQUEST_MASTER_OBJID = QRM.OBJID
|
||||
WHERE QRD.SALES_REQUEST_PART_OBJID = G.OBJID
|
||||
AND QRM.VENDOR_TYPE = 'SUPPLY'
|
||||
AND QRM.VENDOR_OBJID::VARCHAR = G.VENDOR_PM
|
||||
))
|
||||
OR
|
||||
-- 가공업체 견적요청서 생성 가능
|
||||
(MD.PROCESSING_VENDOR IS NOT NULL AND MD.PROCESSING_VENDOR != '' AND NOT EXISTS (
|
||||
(G.PROCESSING_VENDOR IS NOT NULL AND G.PROCESSING_VENDOR != '' AND NOT EXISTS (
|
||||
SELECT 1 FROM QUOTATION_REQUEST_DETAIL QRD
|
||||
INNER JOIN QUOTATION_REQUEST_MASTER QRM ON QRD.QUOTATION_REQUEST_MASTER_OBJID = QRM.OBJID
|
||||
WHERE QRD.SALES_REQUEST_PART_OBJID::VARCHAR = MD.OBJID::VARCHAR
|
||||
WHERE QRD.SALES_REQUEST_PART_OBJID = G.OBJID
|
||||
AND QRM.VENDOR_TYPE = 'PROCESSING'
|
||||
AND QRM.VENDOR_OBJID::VARCHAR = MD.PROCESSING_VENDOR
|
||||
AND QRM.VENDOR_OBJID::VARCHAR = G.PROCESSING_VENDOR
|
||||
))
|
||||
)
|
||||
ORDER BY MD.REGDATE
|
||||
ORDER BY G.PART_NO
|
||||
</select>
|
||||
|
||||
<!-- 구매리스트에서 견적요청서 생성 대상 조회 - 수동 작성 (SALES_REQUEST_PART에서 조회) -->
|
||||
|
||||
@@ -2142,6 +2142,102 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
|
||||
LOADING_DATE = #{loadingDate}
|
||||
WHERE OBJID::VARCHAR = #{OBJID}
|
||||
</update>
|
||||
|
||||
|
||||
<!--
|
||||
/**
|
||||
* 매출마감 전표연동용 데이터 조회
|
||||
* OBJID 리스트로 전표 생성에 필요한 모든 정보를 일괄 조회한다.
|
||||
* @since 2026.02.24
|
||||
* @author system
|
||||
* @version 1.0
|
||||
**/
|
||||
-->
|
||||
<select id="getErpAccountCode" parameterType="map" resultType="com.pms.common.UpperKeyMap">
|
||||
/* salesNcollectMgmt.getErpAccountCode - ERP 계정과목코드 조회 */
|
||||
SELECT acct_cd AS ACCT_CD, acct_nm AS ACCT_NM
|
||||
FROM erp_acct_code
|
||||
WHERE co_cd = #{coCd}
|
||||
AND acct_nm = #{acctNm}
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<select id="getSlipDataForDeadline" parameterType="map" resultType="com.pms.common.UpperKeyMap">
|
||||
/* salesNcollectMgmt.getSlipDataForDeadline - 매출마감 전표연동용 데이터 조회 */
|
||||
SELECT
|
||||
PM.OBJID,
|
||||
PM.PROJECT_NO,
|
||||
PM.AREA_CD,
|
||||
PM.CUSTOMER_OBJID,
|
||||
PM.PART_NO,
|
||||
PM.PART_NAME,
|
||||
COALESCE(PM.TAX_TYPE, '') AS TAX_TYPE,
|
||||
COALESCE(PM.TAX_INVOICE_DATE, '') AS TAX_INVOICE_DATE,
|
||||
COALESCE(PM.EXPORT_DECL_NO, '') AS EXPORT_DECL_NO,
|
||||
COALESCE(PM.LOADING_DATE, '') AS LOADING_DATE,
|
||||
COALESCE(PM.SALES_STATUS, '') AS SALES_STATUS,
|
||||
/* 거래처 정보: ERP 연동 거래처(C_ 접두어)에서 client_cd 조회 */
|
||||
CASE WHEN PM.CUSTOMER_OBJID LIKE 'C_%' THEN
|
||||
(SELECT C.CLIENT_CD FROM CLIENT_MNG C WHERE 'C_' || C.OBJID::VARCHAR = PM.CUSTOMER_OBJID)
|
||||
ELSE NULL END AS ERP_CLIENT_CD,
|
||||
CASE WHEN PM.CUSTOMER_OBJID LIKE 'C_%' THEN
|
||||
(SELECT C.CLIENT_NM FROM CLIENT_MNG C WHERE 'C_' || C.OBJID::VARCHAR = PM.CUSTOMER_OBJID)
|
||||
ELSE
|
||||
(SELECT S.SUPPLY_NAME FROM SUPPLY_MNG S WHERE S.OBJID::VARCHAR = PM.CUSTOMER_OBJID::VARCHAR)
|
||||
END AS CUSTOMER_NAME,
|
||||
CASE WHEN PM.CUSTOMER_OBJID LIKE 'C_%' THEN
|
||||
(SELECT C.BUS_REG_NO FROM CLIENT_MNG C WHERE 'C_' || C.OBJID::VARCHAR = PM.CUSTOMER_OBJID)
|
||||
ELSE NULL END AS BUS_REG_NO,
|
||||
/* 판매 금액 정보 */
|
||||
COALESCE(SR.SALES_SUPPLY_PRICE, 0) AS SALES_SUPPLY_PRICE,
|
||||
COALESCE(SR.SALES_VAT, 0) AS SALES_VAT,
|
||||
COALESCE(SR.SALES_TOTAL_AMOUNT, 0) AS SALES_TOTAL_AMOUNT,
|
||||
/* 환종/환율 (해외) */
|
||||
COALESCE(SR.SALES_CURRENCY, PM.CONTRACT_CURRENCY) AS SALES_CURRENCY,
|
||||
COALESCE(SR.SALES_EXCHANGE_RATE, 0) AS SALES_EXCHANGE_RATE,
|
||||
/* 외화 총액 계산: 원화총액 / 환율 (환율이 0이 아닐 때) */
|
||||
CASE WHEN COALESCE(SR.SALES_EXCHANGE_RATE, 0) > 0 THEN
|
||||
ROUND(COALESCE(SR.SALES_TOTAL_AMOUNT, 0)::NUMERIC / SR.SALES_EXCHANGE_RATE::NUMERIC, 2)
|
||||
ELSE 0 END AS FOREIGN_AMOUNT
|
||||
FROM PROJECT_MGMT PM
|
||||
LEFT JOIN SALES_REGISTRATION SR ON PM.PROJECT_NO = SR.PROJECT_NO
|
||||
WHERE PM.OBJID::VARCHAR = #{OBJID}
|
||||
</select>
|
||||
|
||||
<!--
|
||||
/**
|
||||
* 매출마감 전표번호 시퀀스 조회
|
||||
* 동일 일자의 최대 작성번호를 조회하여 다음 번호를 부여한다.
|
||||
* @since 2026.02.24
|
||||
* @author system
|
||||
* @version 1.0
|
||||
**/
|
||||
-->
|
||||
<select id="getNextSlipMenuSq" parameterType="map" resultType="int">
|
||||
/* salesNcollectMgmt.getNextSlipMenuSq - 다음 전표 작성번호 조회 */
|
||||
SELECT COALESCE(MAX(SLIP_MENU_SQ), 0) + 1
|
||||
FROM PROJECT_MGMT
|
||||
WHERE SALES_SLIP_DATE = #{slipDate}
|
||||
AND SALES_SLIP_MENU_SQ IS NOT NULL
|
||||
</select>
|
||||
|
||||
<!--
|
||||
/**
|
||||
* 매출마감 완료 후 전표 정보 저장
|
||||
* @since 2026.02.24
|
||||
* @author system
|
||||
* @version 1.0
|
||||
**/
|
||||
-->
|
||||
<update id="updateSlipInfo" parameterType="map">
|
||||
/* salesNcollectMgmt.updateSlipInfo - 전표 연동 정보 저장 */
|
||||
UPDATE PROJECT_MGMT
|
||||
SET
|
||||
SALES_STATUS = '완료',
|
||||
SALES_DEADLINE_DATE = #{deadlineDate},
|
||||
SALES_SLIP_DATE = #{slipDate},
|
||||
SALES_SLIP_MENU_SQ = #{slipMenuSq}
|
||||
WHERE OBJID::VARCHAR = #{OBJID}
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ package com.pms.salesmgmt.service;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -2382,54 +2383,52 @@ public class SalesMngService {
|
||||
sqlSession.insert("salesMng.insertQuotationRequestMaster", quotationMaster);
|
||||
|
||||
// 4. 선택된 품목들로 견적요청서 상세 생성
|
||||
List<String> partObjids = new ArrayList<String>();
|
||||
// PART_OBJIDS: 각 항목이 ORIGINAL_OBJIDS (콤마구분 MBOM_DETAIL.OBJID) 형태
|
||||
List<String> partOriginalObjidsList = new ArrayList<String>();
|
||||
if(partObjidsJson != null && !partObjidsJson.isEmpty()) {
|
||||
org.codehaus.jackson.map.ObjectMapper mapper = new org.codehaus.jackson.map.ObjectMapper();
|
||||
partObjids = mapper.readValue(partObjidsJson, List.class);
|
||||
partOriginalObjidsList = mapper.readValue(partObjidsJson, List.class);
|
||||
}
|
||||
|
||||
for(String partObjid : partObjids) {
|
||||
// 구매리스트 품목 정보 조회
|
||||
for(String originalObjids : partOriginalObjidsList) {
|
||||
// ORIGINAL_OBJIDS를 개별 OBJID 리스트로 분리하여 그룹핑 조회
|
||||
List<String> objidList = Arrays.asList(originalObjids.split(","));
|
||||
|
||||
Map partParam = new HashMap();
|
||||
partParam.put("OBJID", partObjid);
|
||||
Map partInfo = (Map)sqlSession.selectOne("salesMng.getSalesRequestPartInfo", partParam);
|
||||
partParam.put("OBJID_LIST", objidList);
|
||||
Map partInfo = (Map)sqlSession.selectOne("salesMng.getGroupedSalesRequestPartInfo", partParam);
|
||||
|
||||
if(partInfo != null) {
|
||||
Map detailParam = new HashMap();
|
||||
detailParam.put("OBJID", CommonUtils.createObjId());
|
||||
detailParam.put("QUOTATION_REQUEST_MASTER_OBJID", quotationMasterObjid);
|
||||
detailParam.put("SALES_REQUEST_PART_OBJID", partObjid); // MBOM_DETAIL.OBJID
|
||||
detailParam.put("SALES_REQUEST_PART_OBJID", partInfo.get("OBJID"));
|
||||
detailParam.put("PART_OBJID", partInfo.get("PART_OBJID"));
|
||||
|
||||
// 업체유형에 따라 다른 정보 저장 (단가는 0으로, 견적 수신 후 입력)
|
||||
if("PROCESSING".equals(vendorType)) {
|
||||
// 가공업체: 품번, 품명, 제작수량
|
||||
detailParam.put("PART_NO", partInfo.get("PART_NO"));
|
||||
detailParam.put("PART_NAME", partInfo.get("PART_NAME"));
|
||||
detailParam.put("RAW_MATERIAL", "");
|
||||
detailParam.put("SIZE", "");
|
||||
detailParam.put("QTY", partInfo.get("PRODUCTION_QTY"));
|
||||
} else {
|
||||
// 공급업체: 소재품번 유무에 따라 분기
|
||||
String rawMaterialNo = CommonUtils.checkNull(partInfo.get("RAW_MATERIAL_NO"));
|
||||
|
||||
if(!rawMaterialNo.isEmpty()) {
|
||||
// 소재품번이 있는 경우: 소재품번, 소재재질, 규격, 발주수량
|
||||
detailParam.put("PART_NO", partInfo.get("RAW_MATERIAL_NO"));
|
||||
detailParam.put("PART_NAME", partInfo.get("RAW_MATERIAL"));
|
||||
detailParam.put("RAW_MATERIAL", partInfo.get("RAW_MATERIAL"));
|
||||
detailParam.put("SIZE", partInfo.get("SIZE"));
|
||||
detailParam.put("QTY", partInfo.get("PO_QTY"));
|
||||
} else {
|
||||
// 소재품번이 없는 경우: 부품품번, 부품명, 제작수량
|
||||
detailParam.put("PART_NO", partInfo.get("PART_NO"));
|
||||
detailParam.put("PART_NAME", partInfo.get("PART_NAME"));
|
||||
detailParam.put("RAW_MATERIAL", "");
|
||||
detailParam.put("SIZE", "");
|
||||
detailParam.put("QTY", partInfo.get("PRODUCTION_QTY"));
|
||||
String rawMaterialNo = CommonUtils.checkNull(partInfo.get("RAW_MATERIAL_NO"));
|
||||
|
||||
if(!rawMaterialNo.isEmpty()) {
|
||||
detailParam.put("PART_NO", partInfo.get("RAW_MATERIAL_NO"));
|
||||
detailParam.put("PART_NAME", partInfo.get("RAW_MATERIAL"));
|
||||
detailParam.put("RAW_MATERIAL", partInfo.get("RAW_MATERIAL"));
|
||||
detailParam.put("SIZE", partInfo.get("SIZE"));
|
||||
detailParam.put("QTY", partInfo.get("PO_QTY"));
|
||||
} else {
|
||||
detailParam.put("PART_NO", partInfo.get("PART_NO"));
|
||||
detailParam.put("PART_NAME", partInfo.get("PART_NAME"));
|
||||
detailParam.put("RAW_MATERIAL", "");
|
||||
detailParam.put("SIZE", "");
|
||||
detailParam.put("QTY", partInfo.get("PRODUCTION_QTY"));
|
||||
}
|
||||
}
|
||||
}
|
||||
detailParam.put("UNIT_PRICE", 0); // 단가는 견적 수신 후 입력
|
||||
detailParam.put("UNIT_PRICE", 0);
|
||||
detailParam.put("REMARK", "");
|
||||
|
||||
sqlSession.insert("salesMng.insertQuotationRequestDetail", detailParam);
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.pms.common.SqlMapConfig;
|
||||
import com.pms.common.bean.PersonBean;
|
||||
import com.pms.common.utils.CommonUtils;
|
||||
import com.pms.common.utils.Constants;
|
||||
import com.pms.api.SalesSlipApiClient;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
@@ -983,74 +984,341 @@ public Map<String, Object> saveSaleRegistration(HttpServletRequest request, Map<
|
||||
* @param paramMap
|
||||
* @return
|
||||
*/
|
||||
public Map salesDeadlineConfirm(HttpServletRequest request,Map paramMap){
|
||||
public Map salesDeadlineConfirm(HttpServletRequest request, Map paramMap) {
|
||||
Map resultMap = new HashMap();
|
||||
SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
|
||||
try{
|
||||
System.out.println("===== 매출마감 처리 시작 =====");
|
||||
System.out.println("paramMap: " + paramMap);
|
||||
|
||||
SqlSession sqlSession = null;
|
||||
try {
|
||||
System.out.println("===== 매출마감 + 아마란스 전표연동 시작 =====");
|
||||
|
||||
String objIdListStr = CommonUtils.checkNull(paramMap.get("objIdList"));
|
||||
String deadlineDate = CommonUtils.checkNull(paramMap.get("deadlineDate"));
|
||||
|
||||
System.out.println("objIdListStr: " + objIdListStr);
|
||||
System.out.println("deadlineDate: " + deadlineDate);
|
||||
|
||||
if(objIdListStr == null || objIdListStr.isEmpty()){
|
||||
|
||||
if (objIdListStr == null || objIdListStr.isEmpty()) {
|
||||
resultMap.put("result", false);
|
||||
resultMap.put("msg", "선택된 항목이 없습니다.");
|
||||
System.out.println("에러: 선택된 항목 없음");
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
if(deadlineDate == null || deadlineDate.isEmpty()){
|
||||
if (deadlineDate == null || deadlineDate.isEmpty()) {
|
||||
resultMap.put("result", false);
|
||||
resultMap.put("msg", "매출마감일을 입력해주세요.");
|
||||
System.out.println("에러: 매출마감일 없음");
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
|
||||
String[] targetObjIdList = objIdListStr.split(",");
|
||||
System.out.println("targetObjIdList 길이: " + targetObjIdList.length);
|
||||
|
||||
if(null != targetObjIdList && 0 < targetObjIdList.length){
|
||||
HttpSession session = request.getSession();
|
||||
sqlSession = SqlMapConfig.getInstance().getSqlSession();
|
||||
PersonBean person = (PersonBean)session.getAttribute(Constants.PERSON_BEAN);
|
||||
|
||||
String userId = person.getUserId();
|
||||
System.out.println("userId: " + userId);
|
||||
|
||||
for(int i=0; i<targetObjIdList.length; i++){
|
||||
String objId = CommonUtils.checkNull(targetObjIdList[i]);
|
||||
System.out.println("처리 중 OBJID[" + i + "]: " + objId);
|
||||
|
||||
HashMap sqlParamMap = new HashMap();
|
||||
sqlParamMap.put("OBJID", objId);
|
||||
sqlParamMap.put("userId", userId);
|
||||
sqlParamMap.put("deadlineDate", deadlineDate);
|
||||
|
||||
System.out.println("SQL 실행 전 - sqlParamMap: " + sqlParamMap);
|
||||
int updateCount = sqlSession.update("salesNcollectMgmt.salesDeadlineConfirm", sqlParamMap);
|
||||
System.out.println("SQL 실행 결과 - 업데이트된 행 수: " + updateCount);
|
||||
}
|
||||
sqlSession.commit();
|
||||
System.out.println("커밋 완료");
|
||||
resultMap.put("result", true);
|
||||
resultMap.put("msg", targetObjIdList.length + "건의 매출마감이 완료되었습니다.");
|
||||
if (targetObjIdList == null || targetObjIdList.length == 0) {
|
||||
resultMap.put("result", false);
|
||||
resultMap.put("msg", "선택된 항목이 없습니다.");
|
||||
return resultMap;
|
||||
}
|
||||
System.out.println("===== 매출마감 처리 완료 =====");
|
||||
}catch(Exception e){
|
||||
|
||||
sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
|
||||
|
||||
// 1) 선택된 항목들의 전표 데이터 조회
|
||||
List<Map<String, Object>> slipDataList = new ArrayList<Map<String, Object>>();
|
||||
for (int i = 0; i < targetObjIdList.length; i++) {
|
||||
String objId = CommonUtils.checkNull(targetObjIdList[i]);
|
||||
if (objId.isEmpty()) continue;
|
||||
|
||||
HashMap queryParam = new HashMap();
|
||||
queryParam.put("OBJID", objId);
|
||||
Map<String, Object> slipData = sqlSession.selectOne("salesNcollectMgmt.getSlipDataForDeadline", queryParam);
|
||||
|
||||
if (slipData == null) {
|
||||
resultMap.put("result", false);
|
||||
resultMap.put("msg", "프로젝트 정보를 찾을 수 없습니다. (OBJID: " + objId + ")");
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
// 이미 마감 완료된 건 체크
|
||||
String salesStatus = CommonUtils.checkNull(slipData.get("SALES_STATUS"));
|
||||
if ("완료".equals(salesStatus)) {
|
||||
resultMap.put("result", false);
|
||||
resultMap.put("msg", "이미 매출마감 완료된 건이 포함되어 있습니다. (" + slipData.get("PROJECT_NO") + ")");
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
// 마감정보 등록 여부 체크
|
||||
String taxType = CommonUtils.checkNull(slipData.get("TAX_TYPE"));
|
||||
if (taxType.isEmpty()) {
|
||||
resultMap.put("result", false);
|
||||
resultMap.put("msg", "마감정보를 먼저 등록해주세요. (" + slipData.get("PROJECT_NO") + ")");
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
// ERP 거래처코드 체크
|
||||
String erpClientCd = CommonUtils.checkNull(slipData.get("ERP_CLIENT_CD"));
|
||||
if (erpClientCd.isEmpty()) {
|
||||
resultMap.put("result", false);
|
||||
resultMap.put("msg", "ERP 연동 거래처가 아닙니다. 거래처 정보를 확인해주세요. (" + slipData.get("CUSTOMER_NAME") + ")");
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
slipDataList.add(slipData);
|
||||
}
|
||||
|
||||
if (slipDataList.isEmpty()) {
|
||||
resultMap.put("result", false);
|
||||
resultMap.put("msg", "처리할 데이터가 없습니다.");
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
// 2) 동일 거래처별 그룹핑 + 금액 합산
|
||||
Map<String, List<Map<String, Object>>> groupByCustomer = new HashMap<String, List<Map<String, Object>>>();
|
||||
for (Map<String, Object> data : slipDataList) {
|
||||
String customerKey = CommonUtils.checkNull(data.get("CUSTOMER_OBJID"));
|
||||
if (!groupByCustomer.containsKey(customerKey)) {
|
||||
groupByCustomer.put(customerKey, new ArrayList<Map<String, Object>>());
|
||||
}
|
||||
groupByCustomer.get(customerKey).add(data);
|
||||
}
|
||||
|
||||
// 3) DB에서 계정과목 코드 조회 (erp_acct_code → api11A02로 동기화된 값)
|
||||
SalesSlipApiClient slipApiClient = new SalesSlipApiClient();
|
||||
String erpBaseUrl = "https://erp.rps-korea.com";
|
||||
|
||||
HashMap acctParam = new HashMap();
|
||||
acctParam.put("coCd", SalesSlipApiClient.CO_CD);
|
||||
|
||||
acctParam.put("acctNm", "외상매출금");
|
||||
Map<String, Object> arAcct = sqlSession.selectOne("salesNcollectMgmt.getErpAccountCode", acctParam);
|
||||
acctParam.put("acctNm", "부가세예수금");
|
||||
Map<String, Object> vatAcct = sqlSession.selectOne("salesNcollectMgmt.getErpAccountCode", acctParam);
|
||||
acctParam.put("acctNm", "제품매출");
|
||||
Map<String, Object> salesAcct = sqlSession.selectOne("salesNcollectMgmt.getErpAccountCode", acctParam);
|
||||
|
||||
slipApiClient.setAccountCodes(
|
||||
arAcct != null ? CommonUtils.checkNull(arAcct.get("ACCT_CD")) : null,
|
||||
vatAcct != null ? CommonUtils.checkNull(vatAcct.get("ACCT_CD")) : null,
|
||||
salesAcct != null ? CommonUtils.checkNull(salesAcct.get("ACCT_CD")) : null
|
||||
);
|
||||
|
||||
int successCount = 0;
|
||||
|
||||
for (Map.Entry<String, List<Map<String, Object>>> entry : groupByCustomer.entrySet()) {
|
||||
List<Map<String, Object>> customerItems = entry.getValue();
|
||||
Map<String, Object> firstItem = customerItems.get(0);
|
||||
|
||||
String erpClientCd = CommonUtils.checkNull(firstItem.get("ERP_CLIENT_CD"));
|
||||
String customerName = CommonUtils.checkNull(firstItem.get("CUSTOMER_NAME"));
|
||||
String areaCd = CommonUtils.checkNull(firstItem.get("AREA_CD"));
|
||||
String taxType = CommonUtils.checkNull(firstItem.get("TAX_TYPE"));
|
||||
// 국내/해외는 프로젝트의 AREA_CD로 판단, 세무구분(taxFg)은 과세구분(TAX_TYPE)으로 전달
|
||||
boolean isDomestic = "0001220".equals(areaCd);
|
||||
|
||||
// 금액 합산
|
||||
long totalSupplyPrice = 0;
|
||||
long totalVat = 0;
|
||||
long totalAmount = 0;
|
||||
double totalForeignAmount = 0;
|
||||
StringBuilder itemNames = new StringBuilder();
|
||||
|
||||
for (Map<String, Object> item : customerItems) {
|
||||
totalSupplyPrice += toLong(item.get("SALES_SUPPLY_PRICE"));
|
||||
totalVat += toLong(item.get("SALES_VAT"));
|
||||
totalAmount += toLong(item.get("SALES_TOTAL_AMOUNT"));
|
||||
totalForeignAmount += toDouble(item.get("FOREIGN_AMOUNT"));
|
||||
|
||||
if (itemNames.length() > 0) itemNames.append(", ");
|
||||
itemNames.append(CommonUtils.checkNull(item.get("PART_NAME")));
|
||||
}
|
||||
|
||||
// 적요: 다건이면 "품명 외 N건"
|
||||
String itemSummary;
|
||||
if (customerItems.size() == 1) {
|
||||
itemSummary = CommonUtils.checkNull(firstItem.get("PART_NAME"));
|
||||
} else {
|
||||
itemSummary = CommonUtils.checkNull(firstItem.get("PART_NAME")) + " 외 " + (customerItems.size() - 1) + "건";
|
||||
}
|
||||
|
||||
// 전표 JSON 생성
|
||||
String slipJson;
|
||||
String slipDate;
|
||||
String taxFg = SalesSlipApiClient.convertTaxType(taxType);
|
||||
|
||||
if (isDomestic) {
|
||||
// 국내: 세금계산서발행일 기준
|
||||
String taxInvoiceDate = CommonUtils.checkNull(firstItem.get("TAX_INVOICE_DATE"));
|
||||
slipDate = taxInvoiceDate.replace("-", "");
|
||||
if (slipDate.isEmpty()) {
|
||||
resultMap.put("result", false);
|
||||
resultMap.put("msg", "세금계산서 발행일이 없습니다. (" + customerName + ")");
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
String slipTitle = customerName + "_" + itemSummary;
|
||||
|
||||
// 작성번호는 타임스탬프 기반으로 유니크하게 생성
|
||||
int menuSq = generateMenuSq();
|
||||
|
||||
slipJson = slipApiClient.buildDomesticSlipJson(
|
||||
slipDate, menuSq, slipTitle,
|
||||
erpClientCd, customerName,
|
||||
totalAmount, totalVat, totalSupplyPrice,
|
||||
itemSummary, taxFg
|
||||
);
|
||||
|
||||
// API 호출
|
||||
String apiResponse = slipApiClient.registerSalesSlip(erpBaseUrl, slipJson);
|
||||
System.out.println("[매출마감] 국내 전표 API 응답: " + apiResponse);
|
||||
|
||||
// 응답 검증
|
||||
validateApiResponse(apiResponse, customerName);
|
||||
|
||||
// DB 업데이트: 각 항목에 전표 정보 저장
|
||||
for (Map<String, Object> item : customerItems) {
|
||||
HashMap updateParam = new HashMap();
|
||||
updateParam.put("OBJID", CommonUtils.checkNull(item.get("OBJID")));
|
||||
updateParam.put("deadlineDate", deadlineDate);
|
||||
updateParam.put("slipDate", slipDate);
|
||||
updateParam.put("slipMenuSq", menuSq);
|
||||
sqlSession.update("salesNcollectMgmt.updateSlipInfo", updateParam);
|
||||
}
|
||||
|
||||
} else {
|
||||
// 해외: 선적일자 기준
|
||||
String loadingDate = CommonUtils.checkNull(firstItem.get("LOADING_DATE"));
|
||||
slipDate = loadingDate.replace("-", "");
|
||||
if (slipDate.isEmpty()) {
|
||||
resultMap.put("result", false);
|
||||
resultMap.put("msg", "선적일자가 없습니다. (" + customerName + ")");
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
String salesCurrency = CommonUtils.checkNull(firstItem.get("SALES_CURRENCY"));
|
||||
double exchangeRate = toDouble(firstItem.get("SALES_EXCHANGE_RATE"));
|
||||
String exportDeclNo = CommonUtils.checkNull(firstItem.get("EXPORT_DECL_NO"));
|
||||
String exchCd = SalesSlipApiClient.convertCurrencyCode(salesCurrency);
|
||||
|
||||
String slipTitle = customerName + "_" + itemSummary
|
||||
+ "_" + String.format("%.2f", totalForeignAmount)
|
||||
+ "_@" + String.format("%.2f", exchangeRate);
|
||||
|
||||
int menuSq = generateMenuSq();
|
||||
|
||||
slipJson = slipApiClient.buildOverseasSlipJson(
|
||||
slipDate, menuSq, slipTitle,
|
||||
erpClientCd, customerName,
|
||||
totalAmount,
|
||||
exchCd, exchangeRate, totalForeignAmount,
|
||||
exportDeclNo, slipDate,
|
||||
customerName + "_" + itemSummary + "_" + String.format("%.2f", totalForeignAmount) + "_@" + String.format("%.2f", exchangeRate),
|
||||
taxFg
|
||||
);
|
||||
|
||||
String apiResponse = slipApiClient.registerSalesSlip(erpBaseUrl, slipJson);
|
||||
System.out.println("[매출마감] 해외 전표 API 응답: " + apiResponse);
|
||||
|
||||
validateApiResponse(apiResponse, customerName);
|
||||
|
||||
for (Map<String, Object> item : customerItems) {
|
||||
HashMap updateParam = new HashMap();
|
||||
updateParam.put("OBJID", CommonUtils.checkNull(item.get("OBJID")));
|
||||
updateParam.put("deadlineDate", deadlineDate);
|
||||
updateParam.put("slipDate", slipDate);
|
||||
updateParam.put("slipMenuSq", menuSq);
|
||||
sqlSession.update("salesNcollectMgmt.updateSlipInfo", updateParam);
|
||||
}
|
||||
}
|
||||
|
||||
successCount += customerItems.size();
|
||||
}
|
||||
|
||||
sqlSession.commit();
|
||||
resultMap.put("result", true);
|
||||
resultMap.put("msg", successCount + "건의 매출마감이 완료되었습니다. (전표 " + groupByCustomer.size() + "건 등록)");
|
||||
System.out.println("===== 매출마감 + 아마란스 전표연동 완료 =====");
|
||||
|
||||
} catch (Exception e) {
|
||||
resultMap.put("result", false);
|
||||
resultMap.put("msg", "매출마감 처리 중 오류가 발생했습니다.");
|
||||
sqlSession.rollback();
|
||||
String errorMsg = e.getMessage();
|
||||
if (errorMsg != null && errorMsg.startsWith("[전표오류]")) {
|
||||
resultMap.put("msg", errorMsg);
|
||||
} else {
|
||||
resultMap.put("msg", "매출마감 처리 중 오류가 발생했습니다: " + errorMsg);
|
||||
}
|
||||
if (sqlSession != null) sqlSession.rollback();
|
||||
System.out.println("===== 매출마감 처리 중 예외 발생 =====");
|
||||
e.printStackTrace();
|
||||
}finally{
|
||||
sqlSession.close();
|
||||
} finally {
|
||||
if (sqlSession != null) sqlSession.close();
|
||||
}
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 아마란스 API 응답 검증
|
||||
* resultCode가 0이 아니면 예외 발생
|
||||
*/
|
||||
private void validateApiResponse(String apiResponse, String customerName) throws Exception {
|
||||
if (apiResponse == null || apiResponse.isEmpty()) {
|
||||
throw new Exception("[전표오류] 아마란스 API 응답이 없습니다. (" + customerName + ")");
|
||||
}
|
||||
// resultCode 추출 (간이 JSON 파싱)
|
||||
String resultCode = extractJsonField(apiResponse, "resultCode");
|
||||
if (resultCode != null && !"0".equals(resultCode.trim())) {
|
||||
String resultMsg = extractJsonField(apiResponse, "resultMsg");
|
||||
String errorDetail = extractJsonField(apiResponse, "errorMsg");
|
||||
StringBuilder errMsg = new StringBuilder("[전표오류] 아마란스 전표 등록 실패 (");
|
||||
errMsg.append(customerName).append("): ");
|
||||
if (resultMsg != null) errMsg.append(resultMsg);
|
||||
if (errorDetail != null) errMsg.append(" - ").append(errorDetail);
|
||||
throw new Exception(errMsg.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON 문자열에서 특정 필드 값 추출 (간이 파서)
|
||||
*/
|
||||
private String extractJsonField(String json, String fieldName) {
|
||||
String searchKey = "\"" + fieldName + "\"";
|
||||
int keyIdx = json.indexOf(searchKey);
|
||||
if (keyIdx < 0) return null;
|
||||
int colonIdx = json.indexOf(":", keyIdx + searchKey.length());
|
||||
if (colonIdx < 0) return null;
|
||||
int valueStart = colonIdx + 1;
|
||||
// 공백 스킵
|
||||
while (valueStart < json.length() && json.charAt(valueStart) == ' ') valueStart++;
|
||||
if (valueStart >= json.length()) return null;
|
||||
char firstChar = json.charAt(valueStart);
|
||||
if (firstChar == '"') {
|
||||
int valueEnd = json.indexOf("\"", valueStart + 1);
|
||||
if (valueEnd < 0) return null;
|
||||
return json.substring(valueStart + 1, valueEnd);
|
||||
} else {
|
||||
int valueEnd = valueStart;
|
||||
while (valueEnd < json.length() && json.charAt(valueEnd) != ','
|
||||
&& json.charAt(valueEnd) != '}' && json.charAt(valueEnd) != ']') {
|
||||
valueEnd++;
|
||||
}
|
||||
return json.substring(valueStart, valueEnd).trim();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 전표 작성번호 생성 (타임스탬프 기반, 밀리초 하위 5자리)
|
||||
*/
|
||||
private int generateMenuSq() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
return (int) (timestamp % 100000);
|
||||
}
|
||||
|
||||
private long toLong(Object value) {
|
||||
if (value == null) return 0;
|
||||
try {
|
||||
return new BigDecimal(value.toString()).longValue();
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private double toDouble(Object value) {
|
||||
if (value == null) return 0.0;
|
||||
try {
|
||||
return new BigDecimal(value.toString()).doubleValue();
|
||||
} catch (Exception e) {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 모든 분할 출하의 총 판매 수량 조회
|
||||
|
||||
Reference in New Issue
Block a user