323 lines
14 KiB
Java
323 lines
14 KiB
Java
package com.wb.domainserver.acidlivy;
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import com.wb.domainserver.util.dataaccess.DataMap;
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.springframework.batch.core.StepContribution;
|
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
|
import org.springframework.batch.repeat.RepeatStatus;
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
import org.springframework.http.*;
|
|
import org.springframework.stereotype.Component;
|
|
import org.springframework.transaction.PlatformTransactionManager;
|
|
import org.springframework.transaction.TransactionDefinition;
|
|
import org.springframework.transaction.support.TransactionTemplate;
|
|
import org.springframework.web.client.RestTemplate;
|
|
|
|
import java.text.SimpleDateFormat;
|
|
import java.util.Date;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
// ACI 출고자료 조회 및 출고처리 배치 Tasklet
|
|
// 흐름: gc_order_shipping(ORDR_STTUS_CD=35) 조회 → 토큰 발급 → ACI API 호출 → BLNO 추출 → 출고처리
|
|
@Slf4j
|
|
@Component
|
|
@RequiredArgsConstructor
|
|
public class AciDlivyTasklet implements Tasklet {
|
|
|
|
private final AciDlivyMapper aciDlivyMapper;
|
|
private final PlatformTransactionManager transactionManager;
|
|
|
|
@Value("${acikey.url}")
|
|
private String aciKeyUrl;
|
|
|
|
@Value("${aci.dlivy.api-url}")
|
|
private String aciDlivyApiUrl;
|
|
|
|
@Value("${acikey.userid}")
|
|
private String userId;
|
|
|
|
@Value("${acikey.apikey}")
|
|
private String apiKey;
|
|
|
|
private static final String HDRYCPN_CD = "code22"; // ACI 택배사 코드
|
|
private static final String DLIVY_STTUS_CD = "55"; // 출고완료 코드
|
|
|
|
@Override
|
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
|
|
log.info("[AciDlivyBatch] 배치 시작");
|
|
|
|
// 토큰 발급 (yaml 고정 apikey → ACIKey 토큰 발급, 메모리에만 유지)
|
|
String aciKeyToken = issueAciKeyToken();
|
|
|
|
// 대상 주문 목록 조회 (ORDR_STTUS_CD = '35')
|
|
List<AciDlivyOrderVO> targetList = aciDlivyMapper.selectAciDlivyTargetList();
|
|
log.info("[AciDlivyBatch] 대상 주문 건수: {}", targetList.size());
|
|
|
|
if (targetList.isEmpty()) {
|
|
log.info("[AciDlivyBatch] 처리할 대상 주문이 없습니다.");
|
|
return RepeatStatus.FINISHED;
|
|
}
|
|
|
|
int successCnt = 0;
|
|
int failCnt = 0;
|
|
int skipCnt = 0;
|
|
|
|
// 건별로 독립 트랜잭션 (REQUIRES_NEW) 으로 처리 - 한 건 실패가 다른 건에 영향 없도록
|
|
TransactionTemplate txTemplate = new TransactionTemplate(transactionManager);
|
|
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
|
|
|
for (AciDlivyOrderVO orderVO : targetList) {
|
|
try {
|
|
// 건별 ACI 출고자료 API 호출 → BLNO 추출
|
|
String blno = callAciDlivyApi(orderVO, aciKeyToken);
|
|
|
|
if (blno == null) {
|
|
log.info("[AciDlivyBatch] 스킵 - ORDR_DLVY_INNB={}, ORDR_INNB={} (BLNO 없음 또는 조건 미충족)",
|
|
orderVO.getOrdrDlvyInnb(), orderVO.getOrdrInnb());
|
|
skipCnt++;
|
|
} else {
|
|
orderVO.setBlno(blno);
|
|
orderVO.setHdrycpnCd(HDRYCPN_CD);
|
|
orderVO.setDlivySttusCd(DLIVY_STTUS_CD);
|
|
|
|
// 건별 처리 (독립 트랜잭션)
|
|
txTemplate.execute(status -> {
|
|
processDelivery(orderVO);
|
|
return null;
|
|
});
|
|
|
|
successCnt++;
|
|
log.info("[AciDlivyBatch] 출고처리 완료 - ORDR_DLVY_INNB={}, BLNO={}",
|
|
orderVO.getOrdrDlvyInnb(), blno);
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
log.error("[AciDlivyBatch] 출고처리 실패 - ORDR_DLVY_INNB={}, 오류={}",
|
|
orderVO.getOrdrDlvyInnb(), e.getMessage(), e);
|
|
failCnt++;
|
|
}
|
|
|
|
// 건별 API 호출 간격 유지 (과부하 방지 - 1초 대기)
|
|
try {
|
|
Thread.sleep(1000);
|
|
} catch (InterruptedException ie) {
|
|
Thread.currentThread().interrupt();
|
|
break;
|
|
}
|
|
}
|
|
|
|
log.info("[AciDlivyBatch] 배치 완료 - 성공: {}, 실패: {}, 스킵: {}", successCnt, failCnt, skipCnt);
|
|
return RepeatStatus.FINISHED;
|
|
}
|
|
|
|
/**
|
|
* ACIKey 토큰 발급
|
|
* yaml 고정 apikey를 APIKEY Header로 사용하여 POST 호출 → 발급받은 토큰은 메모리에만 유지
|
|
*/
|
|
private String issueAciKeyToken() {
|
|
try {
|
|
HttpHeaders headers = new HttpHeaders();
|
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
|
headers.set("USERID", userId);
|
|
headers.set("APIKEY", apiKey);
|
|
|
|
HttpEntity<String> requestEntity = new HttpEntity<>("{}", headers);
|
|
|
|
RestTemplate restTemplate = new RestTemplate();
|
|
ResponseEntity<String> response = restTemplate.exchange(
|
|
aciKeyUrl,
|
|
HttpMethod.POST,
|
|
requestEntity,
|
|
String.class
|
|
);
|
|
|
|
ObjectMapper objectMapper = new ObjectMapper();
|
|
Map<String, Object> body = objectMapper.readValue(response.getBody(), Map.class);
|
|
|
|
String status = (String) body.get("Status");
|
|
String aciKey = (String) body.get("ACIKey");
|
|
String errMsg = (String) body.get("ErrMsg");
|
|
|
|
if ("success".equals(status) && aciKey != null) {
|
|
log.info("[AciDlivyBatch] ACIKey 토큰 발급 성공");
|
|
return aciKey;
|
|
} else {
|
|
throw new RuntimeException("ACIKey 토큰 발급 실패. Status: " + status + ", ErrMsg: " + errMsg);
|
|
}
|
|
|
|
} catch (RuntimeException e) {
|
|
throw e;
|
|
} catch (Exception e) {
|
|
throw new RuntimeException("ACIKey 토큰 발급 오류", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ACI 출고자료 API 호출
|
|
* Content-Type이 text/html로 오는 경우를 대비해 JSON을 String으로 받아 ObjectMapper 파싱
|
|
* @param aciKeyToken 발급받은 ACIKey 토큰
|
|
* @return BLNO(운송장번호), 없으면 null
|
|
*/
|
|
private String callAciDlivyApi(AciDlivyOrderVO orderVO, String aciKeyToken) {
|
|
try {
|
|
HttpHeaders headers = new HttpHeaders();
|
|
headers.set("UserID", userId);
|
|
headers.set("ACIKey", aciKeyToken);
|
|
|
|
String url = aciDlivyApiUrl + "?searchType=1&orderNumber=" + orderVO.getOrdrInnb();
|
|
HttpEntity<Void> entity = new HttpEntity<>(headers);
|
|
|
|
RestTemplate restTemplate = new RestTemplate();
|
|
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
|
|
|
|
log.debug("[AciDlivyBatch] API 응답 원문 (ORDR_INNB={}): {}", orderVO.getOrdrInnb(), response.getBody());
|
|
|
|
ObjectMapper objectMapper = new ObjectMapper();
|
|
Map<String, Object> body = objectMapper.readValue(response.getBody(), Map.class);
|
|
|
|
String status = (String) body.get("Status");
|
|
log.debug("[AciDlivyBatch] API Status={} (ORDR_INNB={})", status, orderVO.getOrdrInnb());
|
|
|
|
// 출고/발송완료 상태 검증 (정확한 Status값은 실제 응답 확인 후 조정 필요)
|
|
if (!"A10".equals(status)) {
|
|
log.info("[AciDlivyBatch] API 출고 미완료 - ORDR_INNB={}, Status={}", orderVO.getOrdrInnb(), status);
|
|
return null;
|
|
}
|
|
|
|
// Data 파싱 (단일 Map 또는 List<Map> 모두 대응)
|
|
Object dataObj = body.get("Data");
|
|
if (dataObj == null) {
|
|
return null;
|
|
}
|
|
|
|
List<Map<String, Object>> dataList;
|
|
if (dataObj instanceof List) {
|
|
dataList = (List<Map<String, Object>>) dataObj;
|
|
} else if (dataObj instanceof Map) {
|
|
dataList = List.of((Map<String, Object>) dataObj);
|
|
} else {
|
|
return null;
|
|
}
|
|
|
|
// OverseaSellerNo == ORDR_DLVY_INNB 매칭 → BLNO 추출
|
|
for (Map<String, Object> item : dataList) {
|
|
Object sellerNo = item.get("OverseaSellerNo");
|
|
if (sellerNo != null && orderVO.getOrdrDlvyInnb().equals(String.valueOf(sellerNo).trim())) {
|
|
Object blno = item.get("BLNO");
|
|
if (blno != null && !String.valueOf(blno).isBlank()) {
|
|
return String.valueOf(blno).trim();
|
|
}
|
|
}
|
|
}
|
|
|
|
log.info("[AciDlivyBatch] OverseaSellerNo 매칭 없음 - ORDR_DLVY_INNB={}", orderVO.getOrdrDlvyInnb());
|
|
return null;
|
|
|
|
} catch (Exception e) {
|
|
log.error("[AciDlivyBatch] API 호출 오류 - ORDR_INNB={}, 오류={}", orderVO.getOrdrInnb(), e.getMessage(), e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 출고처리 (순서: gc_goods_export → gc_goods_export_item → gc_order_item_option
|
|
* → gc_order_shipping → gc_order → 출고로그 → 주문로그)
|
|
* gc_order_shipping/gc_order 상태변경은 모든 아이템 처리 완료 후 일괄 수행
|
|
*/
|
|
private void processDelivery(AciDlivyOrderVO orderVO) {
|
|
|
|
// ① 주문 상품 옵션 목록 먼저 조회 (0건이면 출고처리 전체 스킵)
|
|
List<DataMap> itemList = aciDlivyMapper.selectAciDlivyTargetItemList(orderVO);
|
|
if (itemList.isEmpty()) {
|
|
log.warn("[AciDlivyBatch] 주문 상품 옵션 없음 - 출고처리 스킵 (ORDR_DLVY_INNB={})", orderVO.getOrdrDlvyInnb());
|
|
return;
|
|
}
|
|
log.debug("[AciDlivyBatch] 주문 상품 옵션 수={} (ORDR_DLVY_INNB={})", itemList.size(), orderVO.getOrdrDlvyInnb());
|
|
|
|
// ② DLIVY_INNB = ORDR_DLVY_INNB (gc_order_shipping.ORDR_DLVY_INNB → gc_goods_export.DLIVY_INNB)
|
|
orderVO.setDlivyInnb(orderVO.getOrdrDlvyInnb());
|
|
|
|
// ③ dlivyCdNb 생성 (DLIVY_INNB 확정 후, INSERT 전)
|
|
String dlivyCdNb = "D" + new SimpleDateFormat("yyMMddHH").format(new Date()) + orderVO.getDlivyInnb();
|
|
orderVO.setDlivyCdNb(dlivyCdNb);
|
|
|
|
// ④ gc_goods_export INSERT/UPDATE (dlivyCdNb 직접 포함)
|
|
int exportCnt = aciDlivyMapper.selectGoodsExportCount(orderVO);
|
|
if (exportCnt > 0) {
|
|
aciDlivyMapper.updateGoodsExport(orderVO);
|
|
} else {
|
|
aciDlivyMapper.insertGoodsExport(orderVO);
|
|
}
|
|
|
|
// ⑤ 주문 상품 옵션별 처리 (gc_goods_export_item, gc_order_item_option)
|
|
for (DataMap itemMap : itemList) {
|
|
Object qyObj = itemMap.get("qy");
|
|
if (qyObj == null) continue;
|
|
int qy;
|
|
try {
|
|
qy = Integer.parseInt(String.valueOf(qyObj));
|
|
} catch (NumberFormatException e) {
|
|
continue;
|
|
}
|
|
if (qy <= 0) continue;
|
|
|
|
itemMap.put("sopmalCd", orderVO.getSopmalCd());
|
|
itemMap.put("ordrDlvyInnb", orderVO.getOrdrDlvyInnb());
|
|
itemMap.put("dlivyInnb", orderVO.getDlivyInnb());
|
|
itemMap.put("ordrInnb", orderVO.getOrdrInnb());
|
|
itemMap.put("hdrycpnCd", HDRYCPN_CD);
|
|
itemMap.put("invcnb", orderVO.getBlno());
|
|
itemMap.put("dlivySttusCd", DLIVY_STTUS_CD);
|
|
|
|
// gc_goods_export_item 처리
|
|
int exportItemCnt = aciDlivyMapper.selectGoodsExportItemCount(itemMap);
|
|
if (exportItemCnt > 0) {
|
|
aciDlivyMapper.updateGoodsExportItem(itemMap);
|
|
} else {
|
|
aciDlivyMapper.insertGoodsExportItem(itemMap);
|
|
}
|
|
|
|
// gc_order_item_option 상태 UPDATE
|
|
aciDlivyMapper.updateOrderItemOptionStatus(itemMap);
|
|
|
|
// 출고 로그 INSERT (건별)
|
|
DataMap exportLogMap = new DataMap();
|
|
exportLogMap.put("ordrInnb", orderVO.getOrdrInnb());
|
|
exportLogMap.put("ordrDlvyInnb", orderVO.getOrdrDlvyInnb());
|
|
exportLogMap.put("sopmalCd", orderVO.getSopmalCd());
|
|
exportLogMap.put("dlivyInnb", orderVO.getDlivyInnb());
|
|
exportLogMap.put("dlivyGoodsCdNb", itemMap.get("ordrGoodsOptnInnb"));
|
|
exportLogMap.put("reqstProcsSttusCd", DLIVY_STTUS_CD);
|
|
exportLogMap.put("mssageCn", "[ACI 배치] ORDR_INNB:" + orderVO.getOrdrInnb() + " 출고처리");
|
|
exportLogMap.put("regusr", 0);
|
|
aciDlivyMapper.insertGoodsExportLog(exportLogMap);
|
|
}
|
|
|
|
// ⑥ gc_order_shipping 상태 UPDATE (모든 item_option 처리 완료 후 일괄)
|
|
aciDlivyMapper.updateOrderShippingStatus(orderVO);
|
|
|
|
// ⑦ gc_order 상태 UPDATE (모든 shipping 처리 완료 후 일괄)
|
|
aciDlivyMapper.updateOrderStatus(orderVO);
|
|
|
|
// ⑧ 주문 로그 INSERT
|
|
DataMap orderLogMap = new DataMap();
|
|
orderLogMap.put("ordrInnb", orderVO.getOrdrInnb());
|
|
orderLogMap.put("sopmalCd", orderVO.getSopmalCd());
|
|
orderLogMap.put("logSj", "출고처리(" + dlivyCdNb + ")");
|
|
orderLogMap.put("logDtlCn", "[ACI 배치] 배치 출고처리 완료");
|
|
orderLogMap.put("aditInfoCn", null);
|
|
orderLogMap.put("dlivyCdNb", dlivyCdNb);
|
|
orderLogMap.put("refundCdNb", null);
|
|
orderLogMap.put("logDvCd", "01");
|
|
orderLogMap.put("dvCd", "BATCH");
|
|
orderLogMap.put("opetrNm", "ACI 배치");
|
|
orderLogMap.put("qy", 0);
|
|
orderLogMap.put("amt", 0);
|
|
aciDlivyMapper.insertOrderLog(orderLogMap);
|
|
}
|
|
}
|