fix: ACI POD 배송중 추적 및 배송완료 전환 보정

This commit is contained in:
이주일
2026-07-09 21:59:11 +09:00
parent a75cfef5ba
commit c13bbd7e2d
3 changed files with 111 additions and 56 deletions

View File

@@ -30,7 +30,7 @@ import java.math.BigInteger;
import java.util.*; import java.util.*;
// ACI 배송자료 조회 및 배송처리 배치 Tasklet // ACI 배송자료 조회 및 배송처리 배치 Tasklet
// 흐름: gc_order_shipping(DLIVY_STTUS_CD=55) 조회 → 토큰 발급 → ACI API 호출 → BLNO 추출 → 배송처리 // 흐름: gc_goods_export(DLIVY_STTUS_CD=55,65) 조회 -> 토큰 발급 -> ACI API 호출 -> BLNO 검증 -> 배송처리
@Slf4j @Slf4j
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
@@ -57,7 +57,9 @@ public class AciPodTasklet implements Tasklet {
@Value("${acikey.apikey}") @Value("${acikey.apikey}")
private String apiKey; private String apiKey;
private static final String DLIVY_STTUS_CD = "55"; // 출고완료 코드 private static final String DLIVY_STTUS_CD_EXPORT_COMPLETE = "55"; // 출고완료 코드
private static final String DLIVY_STTUS_CD_DELIVERY_IN_PROGRESS = "65"; // 배송중 코드
private static final String DLIVY_STTUS_CD_DELIVERY_COMPLETE = "75"; // 배송완료 코드
@Override @Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) { public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
@@ -66,8 +68,8 @@ public class AciPodTasklet implements Tasklet {
// 토큰 발급 (yaml 고정 apikey → ACIKey 토큰 발급, 메모리에만 유지) // 토큰 발급 (yaml 고정 apikey → ACIKey 토큰 발급, 메모리에만 유지)
String aciKeyToken = issueAciKeyToken(); String aciKeyToken = issueAciKeyToken();
// 대상 주문 목록 조회 (DLIVY_STTUS_CD = '55') // 대상 주문 목록 조회 (DLIVY_STTUS_CD IN ('55', '65'))
List<AciDlivyOrderVO> goodsExportList = AciDlivyMapper.selectGoodsExportList(DLIVY_STTUS_CD); List<AciDlivyOrderVO> goodsExportList = AciDlivyMapper.selectGoodsExportList();
log.info("[AciPodBatch] 대상 출고 건수: {}", goodsExportList.size()); log.info("[AciPodBatch] 대상 출고 건수: {}", goodsExportList.size());
if (goodsExportList.isEmpty()) { if (goodsExportList.isEmpty()) {
@@ -87,28 +89,39 @@ public class AciPodTasklet implements Tasklet {
try { try {
// 건별 ACI 출고자료 API 호출 // 건별 ACI 출고자료 API 호출
List<DataMap> podInfoList = callAciPodApi(dlivyOrderVO, aciKeyToken); List<DataMap> podInfoList = callAciPodApi(dlivyOrderVO, aciKeyToken);
if (podInfoList.isEmpty()) { if (CollectionUtils.isEmpty(podInfoList)) {
skipCnt++; skipCnt++;
} else { } else {
boolean dlvyInPrg = podInfoList.stream() boolean dlvyInPrg = podInfoList.stream()
.anyMatch(podInfo -> !isBlank(podInfo, "dateLocalwhin")); .anyMatch(podInfo -> !isBlank(podInfo, "dateLocalwhin"));
boolean dlvyCompt = podInfoList.stream() boolean dlvyCompt = podInfoList.stream()
.allMatch(podInfo -> !isBlank(podInfo, "dateDelivery")); .anyMatch(podInfo -> !isBlank(podInfo, "dateDelivery"));
// 건별 배송처리 // 건별 배송처리
BODlivyUpdtVO boDlivyUpdtVO = new BODlivyUpdtVO(); BODlivyUpdtVO boDlivyUpdtVO = new BODlivyUpdtVO();
boDlivyUpdtVO.setDlivyInnb(dlivyOrderVO.getDlivyInnb()); boDlivyUpdtVO.setDlivyInnb(dlivyOrderVO.getDlivyInnb());
boDlivyUpdtVO.setDlivySttusCd(dlivyOrderVO.getDlivySttusCd()); boDlivyUpdtVO.setDlivySttusCd(dlivyOrderVO.getDlivySttusCd());
boDlivyUpdtVO.setSopmalCd(dlivyOrderVO.getSopmalCd()); boDlivyUpdtVO.setSopmalCd(dlivyOrderVO.getSopmalCd());
if(dlvyInPrg) {
boDlivyUpdtVO.setDlivySttusCd("65"); String currentDlivySttusCd = dlivyOrderVO.getDlivySttusCd();
if (dlvyCompt) {
boDlivyUpdtVO.setDlivySttusCd(DLIVY_STTUS_CD_DELIVERY_COMPLETE);
processDeliveryPod(boDlivyUpdtVO); processDeliveryPod(boDlivyUpdtVO);
} successCnt++;
if(dlvyCompt) { } else if (dlvyInPrg && DLIVY_STTUS_CD_EXPORT_COMPLETE.equals(currentDlivySttusCd)) {
boDlivyUpdtVO.setDlivySttusCd("75"); boDlivyUpdtVO.setDlivySttusCd(DLIVY_STTUS_CD_DELIVERY_IN_PROGRESS);
processDeliveryPod(boDlivyUpdtVO); processDeliveryPod(boDlivyUpdtVO);
successCnt++;
} else {
if (dlvyInPrg && DLIVY_STTUS_CD_DELIVERY_IN_PROGRESS.equals(currentDlivySttusCd)) {
log.info("[AciPodBatch] 스킵 - 이미 배송중 상태 유지 - DLIVY_INNB={}, INVCNB={}",
dlivyOrderVO.getDlivyInnb(), dlivyOrderVO.getInvcnb());
} else {
log.info("[AciPodBatch] 스킵 - ACI 배송상태 판단 불가 - DLIVY_INNB={}, INVCNB={}",
dlivyOrderVO.getDlivyInnb(), dlivyOrderVO.getInvcnb());
}
skipCnt++;
} }
successCnt++;
} }
} catch (Exception e) { } catch (Exception e) {
log.error("[AciPodBatch] 배송처리 실패 - DLIVY_INNB={}, 오류={}", log.error("[AciPodBatch] 배송처리 실패 - DLIVY_INNB={}, 오류={}",
@@ -188,7 +201,7 @@ public class AciPodTasklet implements Tasklet {
HttpEntity<Map<String, Object>> apiRequest = HttpEntity<Map<String, Object>> apiRequest =
new HttpEntity<>(body, headers); new HttpEntity<>(body, headers);
log.info("[AciPodBatch] API 응답: {}", apiRequest.toString()); log.info("[AciPodBatch] API 요청: {}", apiRequest.toString());
RestTemplate restTemplate = new RestTemplate(); RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange( ResponseEntity<String> response = restTemplate.exchange(
@@ -201,70 +214,100 @@ public class AciPodTasklet implements Tasklet {
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(response.getBody()); JsonNode jsonNode = objectMapper.readTree(response.getBody());
log.info("[AciPodBatch] API 응답 Status: {}", jsonNode.get("Status").asText()); String status = getText(jsonNode, "Status");
log.info("[AciPodBatch] API 응답 RstMsg: {}", jsonNode.get("RstMsg").asText()); log.info("[AciPodBatch] API 응답 Status: {}", status);
log.info("[AciPodBatch] API 응답 RstMsg: {}", getText(jsonNode, "RstMsg"));
if (!"success".equals(status)) {
log.info("[AciPodBatch] 스킵 - ACI 응답 Status 실패 - DLIVY_INNB={}, Status={}, ErrMsg={}",
orderVO.getDlivyInnb(), status, getText(jsonNode, "ErrMsg"));
return Collections.emptyList();
}
JsonNode podInfoNode = jsonNode.get("PODInfo"); JsonNode podInfoNode = jsonNode.get("PODInfo");
if(!podInfoNode.isNull()) { if(podInfoNode != null && podInfoNode.isArray() && podInfoNode.size() > 0) {
List<DataMap> podInfoList = new ArrayList<>(); List<DataMap> podInfoList = new ArrayList<>();
for(JsonNode podInfoItem : podInfoNode) { for(JsonNode podInfoItem : podInfoNode) {
String blno = getText(podInfoItem, "BLNO");
if (!Objects.equals(trimToEmpty(orderVO.getInvcnb()), trimToEmpty(blno))) {
log.info("[AciPodBatch] 스킵 - 요청 송장번호와 ACI 응답 BLNO 불일치 - DLIVY_INNB={}, INVCNB={}, BLNO={}",
orderVO.getDlivyInnb(), orderVO.getInvcnb(), blno);
continue;
}
DataMap PODInfo = new DataMap(); DataMap PODInfo = new DataMap();
PODInfo.put("blno", podInfoItem.get("BLNO").asText()); PODInfo.put("blno", blno);
PODInfo.put("dateLocalwhin", podInfoItem.get("DATE_LOCALWHIN").asText()); PODInfo.put("dateLocalwhin", getText(podInfoItem, "DATE_LOCALWHIN"));
PODInfo.put("dateLocaldeparture", podInfoItem.get("DATE_LOCALDEPARTURE").asText()); PODInfo.put("dateLocaldeparture", getText(podInfoItem, "DATE_LOCALDEPARTURE"));
PODInfo.put("dateIcnarrival", podInfoItem.get("DATE_ICNARRIVAL").asText()); PODInfo.put("dateIcnarrival", getText(podInfoItem, "DATE_ICNARRIVAL"));
PODInfo.put("dateCustomes", podInfoItem.get("DATE_CUSTOMES").asText()); PODInfo.put("dateCustomes", getText(podInfoItem, "DATE_CUSTOMES"));
PODInfo.put("dateDelivery", podInfoItem.get("DATE_DELIVERY").asText()); PODInfo.put("dateDelivery", getText(podInfoItem, "DATE_DELIVERY"));
PODInfo.put("recipient", podInfoItem.get("RECIPIENT").asText()); PODInfo.put("recipient", getText(podInfoItem, "RECIPIENT"));
PODInfo.put("wta", podInfoItem.get("WTA").asText()); PODInfo.put("wta", getText(podInfoItem, "WTA"));
PODInfo.put("wtdan", podInfoItem.get("WTDAN").asText()); PODInfo.put("wtdan", getText(podInfoItem, "WTDAN"));
List<DataMap> volumeList = new ArrayList<>(); List<DataMap> volumeList = new ArrayList<>();
for(JsonNode volumeItem : podInfoItem.get("VOLUME")) { JsonNode volumeNode = podInfoItem.get("VOLUME");
DataMap volume = new DataMap(); if (volumeNode != null && volumeNode.isArray()) {
volume.put("width", volumeItem.get("WIDTH").asText()); for(JsonNode volumeItem : volumeNode) {
volume.put("length", volumeItem.get("LENGTH").asText()); DataMap volume = new DataMap();
volume.put("height", volumeItem.get("HEIGHT").asText()); volume.put("width", getText(volumeItem, "WIDTH"));
volume.put("length", getText(volumeItem, "LENGTH"));
volume.put("height", getText(volumeItem, "HEIGHT"));
volumeList.add(volume); volumeList.add(volume);
}
} }
PODInfo.put("volume", volumeList); PODInfo.put("volume", volumeList);
List<List> customsList = new ArrayList<>(); List<List> customsList = new ArrayList<>();
for(JsonNode customsItem : podInfoItem.get("CUSTOMS")) { JsonNode customsNode = podInfoItem.get("CUSTOMS");
List<DataMap> customDetail = new ArrayList<>(); if (customsNode != null && customsNode.isArray()) {
for(JsonNode customItem : customsItem) { for(JsonNode customsItem : customsNode) {
DataMap Custom = new DataMap(); List<DataMap> customDetail = new ArrayList<>();
Custom.put("dateEvent", customItem.get("DATE_EVENT").asText()); for(JsonNode customItem : customsItem) {
Custom.put("traceStatus", customItem.get("TRACE_STATUS").asText()); DataMap Custom = new DataMap();
Custom.put("dateEvent", getText(customItem, "DATE_EVENT"));
Custom.put("traceStatus", getText(customItem, "TRACE_STATUS"));
customDetail.add(Custom); customDetail.add(Custom);
}
customsList.add(customDetail);
} }
customsList.add(customDetail);
} }
PODInfo.put("customs", customsList); PODInfo.put("customs", customsList);
List<DataMap> localPodList = new ArrayList<>(); List<DataMap> localPodList = new ArrayList<>();
for(JsonNode localPodItem : podInfoItem.get("LOCAL_POD")) { JsonNode localPodNode = podInfoItem.get("LOCAL_POD");
DataMap localPod = new DataMap(); if (localPodNode != null && localPodNode.isArray()) {
localPod.put("traceStatus", localPodItem.get("TRACE_STATUS").asText()); for(JsonNode localPodItem : localPodNode) {
localPod.put("dateEvent", localPodItem.get("DATE_EVENT").asText()); DataMap localPod = new DataMap();
localPod.put("position", localPodItem.get("POSITION").asText()); localPod.put("traceStatus", getText(localPodItem, "TRACE_STATUS"));
localPod.put("dateEvent", getText(localPodItem, "DATE_EVENT"));
localPod.put("position", getText(localPodItem, "POSITION"));
localPodList.add(localPod); localPodList.add(localPod);
}
} }
PODInfo.put("localPod", localPodList); PODInfo.put("localPod", localPodList);
podInfoList.add(PODInfo); podInfoList.add(PODInfo);
} }
log.info("[AciPodBatch] API 응답 PODInfo: {}", podInfoList); if (podInfoList.isEmpty()) {
log.info("[AciPodBatch] 스킵 - 요청 송장번호와 일치하는 ACI PODInfo 없음 - DLIVY_INNB={}, INVCNB={}",
orderVO.getDlivyInnb(), orderVO.getInvcnb());
} else {
log.info("[AciPodBatch] API 응답 PODInfo: {}", podInfoList);
}
return podInfoList; return podInfoList;
} else { } else {
log.info("[AciPodBatch] API 응답 ErrMsg: {}", jsonNode.get("ErrMsg").asText()); log.info("[AciPodBatch] 스킵 - ACI PODInfo 없음 - DLIVY_INNB={}, ErrMsg={}",
return null; orderVO.getDlivyInnb(), getText(jsonNode, "ErrMsg"));
return Collections.emptyList();
} }
} catch (Exception e) { } catch (Exception e) {
log.error("[AciPodBatch] API 호출 오류 - DLIVY_INNB={}, 오류={}", orderVO.getDlivyInnb(), e.getMessage(), e); log.error("[AciPodBatch] API 호출 오류 - DLIVY_INNB={}, 오류={}", orderVO.getDlivyInnb(), e.getMessage(), e);
return null; return Collections.emptyList();
} }
} }
@@ -435,4 +478,16 @@ public class AciPodTasklet implements Tasklet {
return podInfo.get(key) == null return podInfo.get(key) == null
|| podInfo.get(key).toString().trim().isEmpty(); || podInfo.get(key).toString().trim().isEmpty();
} }
private String getText(JsonNode jsonNode, String key) {
JsonNode valueNode = jsonNode.get(key);
if (valueNode == null || valueNode.isNull()) {
return "";
}
return valueNode.asText();
}
private String trimToEmpty(String value) {
return value == null ? "" : value.trim();
}
} }

View File

@@ -57,8 +57,8 @@ public interface AciDlivyMapper {
/** GC_ORDER_LOG INSERT */ /** GC_ORDER_LOG INSERT */
int insertOrderLog(DataMap logMap); int insertOrderLog(DataMap logMap);
/** 배송처리 대상 주문 목록 조회 (gc_goods_export.DLIVY_STTUS_CD = '55') */ /** 배송처리 대상 주문 목록 조회 (gc_goods_export.DLIVY_STTUS_CD IN ('55', '65')) */
List<AciDlivyOrderVO> selectGoodsExportList(String dlivySttusCd); List<AciDlivyOrderVO> selectGoodsExportList();
/** 출고 상태 일괄 변경 다건 */ /** 출고 상태 일괄 변경 다건 */
public Integer updateBODlivySttusGetbackUpdtArr(BODlivyUpdtVO dlivyVO); public Integer updateBODlivySttusGetbackUpdtArr(BODlivyUpdtVO dlivyVO);

View File

@@ -367,18 +367,18 @@
]]> ]]>
</insert> </insert>
<!-- 배송처리 대상 주문 목록 조회 (DLIVY_STTUS_CD = '55' 출고완료) --> <!-- 배송처리 대상 주문 목록 조회 (DLIVY_STTUS_CD IN ('55', '65')) -->
<select id="selectGoodsExportList" parameterType="java.lang.String" <select id="selectGoodsExportList" resultType="com.wb.domainserver.acidlivy.vo.AciDlivyOrderVO">
resultType="com.wb.domainserver.acidlivy.vo.AciDlivyOrderVO">
<![CDATA[ <![CDATA[
SELECT SELECT
DLIVY_INNB, DLIVY_INNB,
ORDR_INNB, ORDR_INNB,
ORDR_DLVY_INNB, ORDR_DLVY_INNB,
SOPMAL_CD, SOPMAL_CD,
INVCNB INVCNB,
DLIVY_STTUS_CD
FROM GC_GOODS_EXPORT FROM GC_GOODS_EXPORT
WHERE DLIVY_STTUS_CD = #{param} WHERE DLIVY_STTUS_CD IN ('55', '65')
ORDER BY DLIVY_INNB ASC ORDER BY DLIVY_INNB ASC
]]> ]]>
</select> </select>