From c6a5057ab60600091dfaa38403585509b6184651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EC=98=81?= Date: Wed, 5 Nov 2025 17:48:04 +0900 Subject: [PATCH] =?UTF-8?q?=EC=A0=84=EC=B2=B4=EC=A0=81=EC=9D=B8=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EB=B3=80=EA=B2=BD,=20=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=B8=94=20=EC=A0=80=EC=9E=A5=EC=8B=9C=20=EB=8B=A8=EB=8F=85=20?= =?UTF-8?q?=ED=8A=B8=EB=9E=9C=EC=9E=AD=EC=85=98=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=A7=84=ED=96=89=20=EC=97=90=EB=9F=AC=20=EB=B0=9C=EC=83=9D?= =?UTF-8?q?=EC=8B=9C=EC=97=90=EB=8F=84=20=EC=97=90=EB=9F=AC=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=EB=82=A8=EA=B9=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ddl/vips/seq_car_ledger_frmbk.sql | 11 + ddl/vips/seq_car_ledger_frmbk_dtl.sql | 11 + ddl/vips/tb_car_bass_matter_inqire.sql | 2 +- ddl/vips/tb_car_ledger_frmbk.sql | 87 +++++++ ddl/vips/tb_car_ledger_frmbk_dtl.sql | 23 ++ .../VehicleInterfaceController.java | 12 +- .../mapper/CarLedgerFrmbkMapper.java | 28 +++ .../model/basic/CarBassMatterInqireVO.java | 121 ++++++++++ .../model/ledger/CarLedgerFrmbkDtlVO.java | 72 ++++++ .../model/ledger/CarLedgerFrmbkVO.java | 192 +++++++++++++++ .../CarBassMatterInqireLogService.java | 56 +++++ .../service/CarBassMatterInqireService.java | 142 ++++------- .../service/CarLedgerFrmbkLogService.java | 61 +++++ .../service/CarLedgerFrmbkService.java | 84 +++++++ .../service/VehicleInterfaceService.java | 228 ------------------ .../util/ExceptionDetailUtil.java | 29 +++ .../mapper/CarLedgerFrmbkMapper_maria.xml | 178 ++++++++++++++ 17 files changed, 1012 insertions(+), 325 deletions(-) create mode 100644 ddl/vips/seq_car_ledger_frmbk.sql create mode 100644 ddl/vips/seq_car_ledger_frmbk_dtl.sql create mode 100644 ddl/vips/tb_car_ledger_frmbk.sql create mode 100644 ddl/vips/tb_car_ledger_frmbk_dtl.sql create mode 100644 src/main/java/com/vmis/interfaceapp/mapper/CarLedgerFrmbkMapper.java create mode 100644 src/main/java/com/vmis/interfaceapp/model/ledger/CarLedgerFrmbkDtlVO.java create mode 100644 src/main/java/com/vmis/interfaceapp/model/ledger/CarLedgerFrmbkVO.java create mode 100644 src/main/java/com/vmis/interfaceapp/service/CarBassMatterInqireLogService.java create mode 100644 src/main/java/com/vmis/interfaceapp/service/CarLedgerFrmbkLogService.java create mode 100644 src/main/java/com/vmis/interfaceapp/service/CarLedgerFrmbkService.java delete mode 100644 src/main/java/com/vmis/interfaceapp/service/VehicleInterfaceService.java create mode 100644 src/main/java/com/vmis/interfaceapp/util/ExceptionDetailUtil.java create mode 100644 src/main/resources/mybatis/mapper/CarLedgerFrmbkMapper_maria.xml diff --git a/ddl/vips/seq_car_ledger_frmbk.sql b/ddl/vips/seq_car_ledger_frmbk.sql new file mode 100644 index 0000000..cab1cf6 --- /dev/null +++ b/ddl/vips/seq_car_ledger_frmbk.sql @@ -0,0 +1,11 @@ +-- 자동차 등록 원부(갑) 마스터 ID 시퀀스 +-- CLFB000000000001, CLFB000000000002, ... 형태로 생성 +CREATE SEQUENCE seq_car_ledger_frmbk + START WITH 1 + INCREMENT BY 1 + MINVALUE 1 + MAXVALUE 9999999999 + CACHE 1000 + NOCYCLE; + +-- SELECT CONCAT('CLFB', LPAD(NEXTVAL(seq_car_ledger_frmbk), 16, '0')); diff --git a/ddl/vips/seq_car_ledger_frmbk_dtl.sql b/ddl/vips/seq_car_ledger_frmbk_dtl.sql new file mode 100644 index 0000000..a4fe795 --- /dev/null +++ b/ddl/vips/seq_car_ledger_frmbk_dtl.sql @@ -0,0 +1,11 @@ +-- 자동차 등록 원부(갑) 상세 ID 시퀀스 +-- CLFD000000000001, CLFD000000000002, ... 형태로 생성 +CREATE SEQUENCE seq_car_ledger_frmbk_dtl + START WITH 1 + INCREMENT BY 1 + MINVALUE 1 + MAXVALUE 9999999999 + CACHE 1000 + NOCYCLE; + +-- SELECT CONCAT('CLFD', LPAD(NEXTVAL(seq_car_ledger_frmbk_dtl), 16, '0')); diff --git a/ddl/vips/tb_car_bass_matter_inqire.sql b/ddl/vips/tb_car_bass_matter_inqire.sql index ba3ff3a..2590d46 100644 --- a/ddl/vips/tb_car_bass_matter_inqire.sql +++ b/ddl/vips/tb_car_bass_matter_inqire.sql @@ -14,7 +14,7 @@ create table tb_car_bass_matter_inqire DMND_VHRNO varchar(30) null comment '요청 자동차등록번호', DMND_VIN varchar(17) null comment '요청 차대번호', CNTC_RESULT_CODE varchar(8) null comment '연계 결과 코드', - CNTC_RESULT_DTLS varchar(200) null comment '연계 결과 상세', + CNTC_RESULT_DTLS varchar(4000) null comment '연계 결과 상세', PRYE varchar(4) null comment '연식', REGIST_DE varchar(8) null comment '등록일', ERSR_REGIST_SE_CODE varchar(4) null comment '말소 등록 구분 코드', diff --git a/ddl/vips/tb_car_ledger_frmbk.sql b/ddl/vips/tb_car_ledger_frmbk.sql new file mode 100644 index 0000000..0a292c9 --- /dev/null +++ b/ddl/vips/tb_car_ledger_frmbk.sql @@ -0,0 +1,87 @@ +create table tb_car_ledger_frmbk +( + CAR_LEDGER_FRMBK_ID varchar(20) not null comment '자동차 등록 원부 갑 ID' + primary key, + INFO_SYS_ID varchar(6) null comment '정보 시스템 ID', + INFO_SYS_IP varchar(23) null comment '정보 시스템 IP', + SIGUNGU_CODE varchar(5) null comment '시군구 코드', + CNTC_INFO_CODE varchar(15) null comment '연계 정보 코드', + CHARGER_ID varchar(15) null comment '담당자 ID', + CHARGER_IP varchar(23) null comment '담당자 IP', + CHARGER_NM varchar(75) null comment '담당자명', + DMND_VHRNO varchar(30) null comment '요청 자동차등록번호', + DMND_ONES_INFORMATION_OPEN varchar(1) null comment '요청 개인 정보 공개', + DMND_CPTTR_NM varchar(75) null comment '요청 민원인 성명', + DMND_CPTTR_IHIDNUM varchar(100) null comment '요청 민원인 주민번호', + DMND_CPTTR_LEGALDONG_CODE varchar(10) null comment '요청 민원인 법정동 코드', + DMND_ROUTE_SE_CODE varchar(1) null comment '요청 경로 구분 코드', + DMND_DETAIL_EXPRESSION varchar(1) null comment '요청 내역 표시', + DMND_INQIRE_SE_CODE varchar(1) null comment '요청 조회 구분 코드', + CNTC_RESULT_CODE varchar(8) null comment '연계 결과 코드', + CNTC_RESULT_DTLS varchar(4000) null comment '연계 결과 상세', + LEDGER_GROUP_NO varchar(6) null comment '원부 그룹 번호', + LEDGER_INDVDLZ_NO varchar(6) null comment '원부 개별 번호', + VHMNO varchar(20) null comment '차량관리번호', + VHRNO varchar(30) null comment '차량등록번호', + VIN varchar(17) null comment '차대번호', + VHCTY_ASORT_CODE varchar(1) null comment '차종 종별 코드', + VHCTY_ASORT_NM varchar(30) null comment '차종 종별명', + CNM varchar(75) null comment '차명', + COLOR_CODE varchar(2) null comment '색상 코드', + COLOR_NM varchar(30) null comment '색상명', + NMPL_STNDRD_CODE varchar(1) null comment '번호판 규격 코드', + NMPL_STNDRD_NM varchar(30) null comment '번호판 규격명', + PRPOS_SE_CODE varchar(2) null comment '용도 구분 코드', + PRPOS_SE_NM varchar(20) null comment '용도 구분명', + MTRS_FOM_NM varchar(75) null comment '원동기 형식명', + FOM_NM varchar(75) null comment '형식명', + ACQS_AMOUNT varchar(50) null comment '취득 금액', + REGIST_DETAIL_CODE varchar(8) null comment '등록 상세 코드', + REGIST_DETAIL_NM varchar(30) null comment '등록 상세명', + FRST_REGIST_DE varchar(8) null comment '최초 등록일', + CAAG_ENDDE varchar(8) null comment '차령 종료일', + PRYE varchar(4) null comment '연식', + SPMNNO1 varchar(3) null comment '제원관리번호1', + SPMNNO2 varchar(14) null comment '제원관리번호2', + YBL_MD varchar(8) null comment '제작 년월일', + TRVL_DSTNC varchar(10) null comment '주행 거리', + INSPT_VALID_PD_BGNDE varchar(8) null comment '검사 유효 기간 시작일', + INSPT_VALID_PD_ENDDE varchar(8) null comment '검사 유효 기간 종료일', + CHCK_VALID_PD_BGNDE varchar(8) null comment '점검 유효 기간 시작일', + CHCK_VALID_PD_ENDDE varchar(8) null comment '점검 유효 기간 종료일', + REGIST_REQST_SE_NM varchar(75) null comment '등록 신청 구분명', + FRST_REGIST_RQRCNO varchar(20) null comment '최초 등록 접수번호', + NMPL_CSDY_REMNR_DE varchar(8) null comment '번호판 영치 최고일', + NMPL_CSDY_AT varchar(1) null comment '번호판 영치 여부', + BSS_USE_PD varchar(30) null comment '사업용 사용 기간', + OCTHT_ERSR_PRVNTC_NTICE_DE varchar(8) null comment '직권 말소 예고 통지일', + ERSR_REGIST_DE varchar(8) null comment '말소 등록일', + ERSR_REGIST_SE_CODE varchar(4) null comment '말소 등록 구분 코드', + ERSR_REGIST_SE_NM varchar(200) null comment '말소 등록 구분명', + MRTGCNT varchar(4) null comment '저당수', + VHCLECNT varchar(4) null comment '압류건수', + STMDCNT varchar(4) null comment '구조변경수', + ADRES1 varchar(750) null comment '사용 본거지 주소', + ADRES_NM1 varchar(300) null comment '사용 본거지 주소상세', + ADRES varchar(750) null comment '소유자 주소', + ADRES_NM varchar(300) null comment '소유자 주소상세', + INDVDL_BSNM_AT varchar(1) null comment '개인 사업자 여부', + TELNO varchar(30) null comment '대표소유자 전화번호', + MBER_NM varchar(75) null comment '대표소유자 성명', + MBER_SE_CODE varchar(2) null comment '대표소유자 회원 구분 코드', + MBER_SE_NO varchar(100) null comment '대표소유자 회원 번호', + TAXXMPT_TRGTER_SE_CODE varchar(2) null comment '비과세 대상 구분 코드', + TAXXMPT_TRGTER_SE_CODE_NM varchar(30) null comment '비과세 대상 구분 코드명', + CNT_MATTER varchar(5) null comment '특기사항 건수', + EMD_NM varchar(75) null comment '사용 본거지 행정동명', + PRVNTCCNT varchar(4) null comment '예고수', + XPORT_FLFL_AT_STTEMNT_DE varchar(8) null comment '수출 이행 여부 신고일', + PARTN_RQRCNO varchar(13) null comment '발급번호', + FRST_TRNSFR_DE varchar(8) null comment '최초 양도일', + PROCESS_IMPRTY_RESN_CODE varchar(2) null comment '처리 불가 사유 코드', + PROCESS_IMPRTY_RESN_DTLS varchar(200) null comment '처리 불가 사유 명세', + REG_DT datetime null comment '등록 일시', + RGTR varchar(11) null comment '등록자' +) + comment '자동차 등록 원부 갑'; + diff --git a/ddl/vips/tb_car_ledger_frmbk_dtl.sql b/ddl/vips/tb_car_ledger_frmbk_dtl.sql new file mode 100644 index 0000000..ed0af0a --- /dev/null +++ b/ddl/vips/tb_car_ledger_frmbk_dtl.sql @@ -0,0 +1,23 @@ +create table tb_car_ledger_frmbk_dtl +( + CAR_LEDGER_FRMBK_DTL_ID varchar(20) not null comment '자동차 등록 원부 갑 상세 ID' + primary key, + CAR_LEDGER_FRMBK_ID varchar(20) null comment '자동차 등록 원부 갑 ID', + MAINCHK varchar(2) null comment '해제여부', + CHANGE_JOB_SE_CODE varchar(2) null comment '변경 업무 구분 코드', + MAINNO varchar(4) null comment '주번호', + SUBNO varchar(4) null comment '부번호', + DTLS varchar(2000) null comment '사항란', + RQRCNO varchar(20) null comment '접수번호', + VHMNO varchar(20) null comment '차량관리번호', + LEDGER_GROUP_NO varchar(6) null comment '원부 그룹 번호', + LEDGER_INDVDLZ_NO varchar(6) null comment '원부 개별 번호', + GUBUN_NM varchar(75) null comment '변경 업무 구분명', + CHANGE_DE varchar(8) null comment '변경 일자', + DETAIL_SN varchar(6) null comment '상세 순번', + FLAG varchar(3) null comment '표기여부', + REG_DT datetime null comment '등록 일시', + RGTR varchar(11) null comment '등록자' +) + comment '자동차 등록 원부 갑 상세'; + diff --git a/src/main/java/com/vmis/interfaceapp/controller/VehicleInterfaceController.java b/src/main/java/com/vmis/interfaceapp/controller/VehicleInterfaceController.java index f86d4e5..8f7f669 100644 --- a/src/main/java/com/vmis/interfaceapp/controller/VehicleInterfaceController.java +++ b/src/main/java/com/vmis/interfaceapp/controller/VehicleInterfaceController.java @@ -5,7 +5,8 @@ import com.vmis.interfaceapp.model.basic.BasicResponse; import com.vmis.interfaceapp.model.common.Envelope; import com.vmis.interfaceapp.model.ledger.LedgerRequest; import com.vmis.interfaceapp.model.ledger.LedgerResponse; -import com.vmis.interfaceapp.service.VehicleInterfaceService; +import com.vmis.interfaceapp.service.CarBassMatterInqireService; +import com.vmis.interfaceapp.service.CarLedgerFrmbkService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.ExampleObject; @@ -41,7 +42,7 @@ import org.springframework.web.bind.annotation.*; *

아키텍처 설계:

* * @@ -54,7 +55,8 @@ import org.springframework.web.bind.annotation.*; @Tag(name = "Vehicle Interfaces", description = "시군구연계 자동차 정보 연계 API") public class VehicleInterfaceController { - private final VehicleInterfaceService vehicleService; + private final CarBassMatterInqireService carBassMatterInqireService; + private final CarLedgerFrmbkService carLedgerFrmbkService; /** @@ -132,7 +134,7 @@ public class VehicleInterfaceController { @org.springframework.web.bind.annotation.RequestBody Envelope envelope ) { // 서비스에서 요청 보강/로깅/호출을 모두 오케스트레이션 - return vehicleService.basic(envelope); + return carBassMatterInqireService.basic(envelope); } /** @@ -228,6 +230,6 @@ public class VehicleInterfaceController { @org.springframework.web.bind.annotation.RequestBody Envelope envelope ) { // 서비스에서 요청 보강/호출을 오케스트레이션 - return vehicleService.ledger(envelope); + return carLedgerFrmbkService.ledger(envelope); } } diff --git a/src/main/java/com/vmis/interfaceapp/mapper/CarLedgerFrmbkMapper.java b/src/main/java/com/vmis/interfaceapp/mapper/CarLedgerFrmbkMapper.java new file mode 100644 index 0000000..becfe34 --- /dev/null +++ b/src/main/java/com/vmis/interfaceapp/mapper/CarLedgerFrmbkMapper.java @@ -0,0 +1,28 @@ +package com.vmis.interfaceapp.mapper; + +import com.vmis.interfaceapp.model.ledger.CarLedgerFrmbkDtlVO; +import com.vmis.interfaceapp.model.ledger.CarLedgerFrmbkVO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 자동차 등록 원부(갑) Mapper + */ +@Mapper +public interface CarLedgerFrmbkMapper { + + // ID 시퀀스 + String selectNextCarLedgerFrmbkId(); + String selectNextCarLedgerFrmbkDtlId(); + + // 마스터 INSERT/UPDATE/SELECT + int insertCarLedgerFrmbk(CarLedgerFrmbkVO vo); + int updateCarLedgerFrmbk(CarLedgerFrmbkVO vo); + CarLedgerFrmbkVO selectCarLedgerFrmbkById(String carLedgerFrmbkId); + + // 상세 INSERT (단건) + int insertCarLedgerFrmbkDtl(CarLedgerFrmbkDtlVO vo); + + // 편의: 상세 일괄 (MyBatis foreach를 XML에서 사용할 수도 있으나, 여기서는 단건 호출을 반복) +} diff --git a/src/main/java/com/vmis/interfaceapp/model/basic/CarBassMatterInqireVO.java b/src/main/java/com/vmis/interfaceapp/model/basic/CarBassMatterInqireVO.java index 53e7838..4ee9abf 100644 --- a/src/main/java/com/vmis/interfaceapp/model/basic/CarBassMatterInqireVO.java +++ b/src/main/java/com/vmis/interfaceapp/model/basic/CarBassMatterInqireVO.java @@ -1,5 +1,6 @@ package com.vmis.interfaceapp.model.basic; +import com.vmis.interfaceapp.model.common.Envelope; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -19,6 +20,126 @@ import java.time.LocalDateTime; @AllArgsConstructor public class CarBassMatterInqireVO { + // ==== Static factory/mapping methods (moved from Service) ==== + public static CarBassMatterInqireVO fromRequest(BasicRequest request) { + return CarBassMatterInqireVO.builder() + .infoSysId(request.getInfoSysId()) + .infoSysIp(request.getInfoSysIp()) + .sigunguCode(request.getSigunguCode()) + .cntcInfoCode(request.getCntcInfoCode()) + .chargerId(request.getChargerId()) + .chargerIp(request.getChargerIp()) + .chargerNm(request.getChargerNm()) + .dmndLevyStdde(request.getLevyStdde()) + .dmndInqireSeCode(request.getInqireSeCode()) + .dmndVhrno(request.getVhrno()) + .dmndVin(request.getVin()) + .rgtr("SYSTEM") + .build(); + } + + public static CarBassMatterInqireVO fromResponse(String id, Envelope envelope) { + if (envelope == null || envelope.getData() == null || envelope.getData().isEmpty()) return null; + BasicResponse response = envelope.getData().get(0); + CarBassMatterInqireVO.CarBassMatterInqireVOBuilder builder = CarBassMatterInqireVO.builder() + .carBassMatterInqire(id) + .cntcResultCode(response.getCntcResultCode()) + .cntcResultDtls(response.getCntcResultDtls()); + if (response.getRecord() != null && !response.getRecord().isEmpty()) { + BasicResponse.Record record = response.getRecord().get(0); + applyRecord(builder, record); + } + return builder.build(); + } + + private static void applyRecord(CarBassMatterInqireVO.CarBassMatterInqireVOBuilder builder, BasicResponse.Record record) { + builder + .prye(record.getPrye()) + .registDe(record.getRegistDe()) + .ersrRegistSeCode(record.getErsrRegistSeCode()) + .ersrRegistSeNm(record.getErsrRegistSeNm()) + .ersrRegistDe(record.getErsrRegistDe()) + .registDetailCode(record.getRegistDetailCode()) + .dsplvl(record.getDsplvl()) + .useStrnghldLegaldongCode(record.getUseStrnghldLegaldongCode()) + .useStrnghldAdstrdCode(record.getUseStrnghldAdstrdCode()) + .useStrnghldMntn(record.getUseStrnghldMntn()) + .useStrnghldLnbr(record.getUseStrnghldLnbr()) + .useStrnghldHo(record.getUseStrnghldHo()) + .useStrnghldAdresNm(record.getUseStrnghldAdresNm()) + .useStrnghldRoadNmCode(record.getUseStrnghldRoadNmCode()) + .usgsrhldUndgrndBuldSeCode(record.getUsgsrhldUndgrndBuldSeCode()) + .useStrnghldBuldMainNo(record.getUseStrnghldBuldMainNo()) + .useStrnghldBuldSubNo(record.getUseStrnghldBuldSubNo()) + .usgsrhldAdresFull(record.getUsgsrhldAdresFull()) + .mberSeCode(record.getMberSeCode()) + .mberSeNo(record.getMberSeNo()) + .mberNm(record.getMberNm()) + .telno(record.getTelno()) + .ownerLegaldongCode(record.getOwnerLegaldongCode()) + .ownerAdstrdCode(record.getOwnerAdstrdCode()) + .ownerMntn(record.getOwnerMntn()) + .ownerLnbr(record.getOwnerLnbr()) + .ownerHo(record.getOwnerHo()) + .ownerAdresNm(record.getOwnerAdresNm()) + .ownerRoadNmCode(record.getOwnerRoadNmCode()) + .ownerUndgrndBuldSeCode(record.getOwnerUndgrndBuldSeCode()) + .ownerBuldMainNo(record.getOwnerBuldMainNo()) + .ownerBuldSubNo(record.getOwnerBuldSubNo()) + .ownrWholaddr(record.getOwnerAdresFull()) + .aftrVhrno(record.getAftrVhrno()) + .useFuelCode(record.getUseFuelCode()) + .prposSeCode(record.getPrposSeCode()) + .mtrsFomNm(record.getMtrsFomNm()) + .frntVhrno(record.getFrntVhrno()) + .vhclno(record.getVhrno()) + .vin(record.getVin()) + .cnm(record.getCnm()) + .vhcleTotWt(record.getVhcleTotWt()) + .caagEndde(record.getCaagEndde()) + .changeDe(record.getChangeDe()) + .vhctyAsortCode(record.getVhctyAsortCode()) + .vhctyTyCode(record.getVhctyTyCode()) + .vhctySeCode(record.getVhctySeCode()) + .mxmmLdg(record.getMxmmLdg()) + .vhctyAsortNm(record.getVhctyAsortNm()) + .vhctyTyNm(record.getVhctyTyNm()) + .vhctySeNm(record.getVhctySeNm()) + .frstRegistDe(record.getFrstRegistDe()) + .fomNm(record.getFomNm()) + .acqsDe(record.getAcqsDe()) + .acqsEndDe(record.getAcqsEndDe()) + .yblMd(record.getYblMd()) + .transrRegistDe(record.getTransrRegistDe()) + .spcfRegistSttusCode(record.getSpcfRegistSttusCode()) + .colorNm(record.getColorNm()) + .mrtgCo(record.getMrtgCo()) + .seizrCo(record.getSeizrCo()) + .stmdCo(record.getStmdCo()) + .nmplCsdyAt(record.getNmplCsdyAt()) + .nmplCsdyRemnrDe(record.getNmplCsdyRemnrDe()) + .originSeCode(record.getOriginSeCode()) + .nmplStndrdCode(record.getNmplStndrdCode()) + .acqsAmount(record.getAcqsAmount()) + .insptValidPdBgnde(record.getInsptValidPdBgnde()) + .insptValidPdEndde(record.getInsptValidPdEndde()) + .useStrnghldGrcCode(record.getUseStrnghldGrcCode()) + .tkcarPscapCo(record.getTkcarPscapCo()) + .spmnno(record.getSpmnno()) + .trvlDstnc(record.getTrvlDstnc()) + .frstRegistRqrcno(record.getFrstRegistRqrcno()) + .vlntErsrPrvntcNticeDe(record.getVlntErsrPrvntcNticeDe()) + .registInsttNm(record.getRegistInsttNm()) + .processImprtyResnCode(record.getProcessImprtyResnCode()) + .processImprtyResnDtls(record.getProcessImprtyResnDtls()) + .cbdLt(record.getCbdLt()) + .cbdBt(record.getCbdBt()) + .cbdHg(record.getCbdHg()) + .frstMxmmLdg(record.getFrstMxmmLdg()) + .fuelCnsmpRt(record.getFuelCnsmpRt()) + .elctyCmpndFuelCnsmpRt(record.getElctyCmpndFuelCnsmpRt()); + } + /** * 자동차 기본 사항 조회 ID (PK) * 형식: CBMI000000000001 diff --git a/src/main/java/com/vmis/interfaceapp/model/ledger/CarLedgerFrmbkDtlVO.java b/src/main/java/com/vmis/interfaceapp/model/ledger/CarLedgerFrmbkDtlVO.java new file mode 100644 index 0000000..6a7a9d6 --- /dev/null +++ b/src/main/java/com/vmis/interfaceapp/model/ledger/CarLedgerFrmbkDtlVO.java @@ -0,0 +1,72 @@ +package com.vmis.interfaceapp.model.ledger; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +/** + * 자동차 등록 원부(갑) 상세 엔티티 VO + * + *

tb_car_ledger_frmbk_dtl 테이블 매핑

+ */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CarLedgerFrmbkDtlVO { + + // ==== Static factory/mapping methods (moved from Service) ==== + public static List listFromResponse(LedgerResponse res, String masterId) { + List list = new ArrayList<>(); + if (res == null || res.getRecord() == null) return list; + for (LedgerResponse.Record r : res.getRecord()) { + CarLedgerFrmbkDtlVO vo = CarLedgerFrmbkDtlVO.builder() + .carLedgerFrmbkId(masterId) + .mainchk(r.getMainchk()) + .changeJobSeCode(r.getChangeJobSeCode()) + .mainno(r.getMainno()) + .subno(r.getSubno()) + .dtls(r.getDtls()) + .rqrcno(r.getRqrcno()) + .vhmno(r.getVhmno()) + .ledgerGroupNo(r.getLedgerGroupNo()) + .ledgerIndvdlzNo(r.getLedgerIndvdlzNo()) + .gubunNm(r.getGubunNm()) + .changeDe(r.getChangeDe()) + .detailSn(r.getDetailSn()) + .flag(r.getFlag()) + .rgtr("SYSTEM") + .build(); + list.add(vo); + } + return list; + } + + // PK + private String carLedgerFrmbkDtlId; + + // FK + private String carLedgerFrmbkId; + + // 본문 + private String mainchk; + private String changeJobSeCode; + private String mainno; + private String subno; + private String dtls; + private String rqrcno; + private String vhmno; + private String ledgerGroupNo; + private String ledgerIndvdlzNo; + private String gubunNm; + private String changeDe; + private String detailSn; + private String flag; + + // 감사 + private String rgtr; +} diff --git a/src/main/java/com/vmis/interfaceapp/model/ledger/CarLedgerFrmbkVO.java b/src/main/java/com/vmis/interfaceapp/model/ledger/CarLedgerFrmbkVO.java new file mode 100644 index 0000000..11b1d20 --- /dev/null +++ b/src/main/java/com/vmis/interfaceapp/model/ledger/CarLedgerFrmbkVO.java @@ -0,0 +1,192 @@ +package com.vmis.interfaceapp.model.ledger; + +import com.vmis.interfaceapp.model.ledger.LedgerResponse.Record; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 자동차 등록 원부(갑) 마스터 엔티티 VO + * + *

tb_car_ledger_frmbk 테이블 매핑

+ */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CarLedgerFrmbkVO { + + // ==== Static factory/mapping methods (moved from Service) ==== + public static CarLedgerFrmbkVO fromRequest(com.vmis.interfaceapp.model.ledger.LedgerRequest request) { + return CarLedgerFrmbkVO.builder() + .infoSysId(request.getInfoSysId()) + .infoSysIp(request.getInfoSysIp()) + .sigunguCode(request.getSigunguCode()) + .cntcInfoCode(request.getCntcInfoCode()) + .chargerId(request.getChargerId()) + .chargerIp(request.getChargerIp()) + .chargerNm(request.getChargerNm()) + .dmndVhrno(request.getVhrno()) + .rgtr("SYSTEM") + .build(); + } + + public static CarLedgerFrmbkVO fromResponseMaster(String id, com.vmis.interfaceapp.model.ledger.LedgerResponse res) { + return CarLedgerFrmbkVO.builder() + .carLedgerFrmbkId(id) + .cntcResultCode(res.getCntcResultCode()) + .cntcResultDtls(res.getCntcResultDtls()) + .ledgerGroupNo(res.getLedgerGroupNo()) + .ledgerIndvdlzNo(res.getLedgerIndvdlzNo()) + .vhmno(res.getVhmno()) + .vhrno(res.getVhrno()) + .vin(res.getVin()) + .vhctyAsortCode(res.getVhctyAsortCode()) + .vhctyAsortNm(res.getVhctyAsortNm()) + .cnm(res.getCnm()) + .colorCode(res.getColorCode()) + .colorNm(res.getColorNm()) + .nmplStndrdCode(res.getNmplStndrdCode()) + .nmplStndrdNm(res.getNmplStndrdNm()) + .prposSeCode(res.getPrposSeCode()) + .prposSeNm(res.getPrposSeNm()) + .mtrsFomNm(res.getMtrsFomNm()) + .fomNm(res.getFomNm()) + .acqsAmount(res.getAcqsAmount()) + .registDetailCode(res.getRegistDetailCode()) + .registDetailNm(res.getRegistDetailNm()) + .frstRegistDe(res.getFrstRegistDe()) + .caagEndde(res.getCaagEndde()) + .prye(res.getPrye()) + .spmnno1(res.getSpmnno1()) + .spmnno2(res.getSpmnno2()) + .yblMd(res.getYblMd()) + .trvlDstnc(res.getTrvlDstnc()) + .insptValidPdBgnde(res.getInsptValidPdBgnde()) + .insptValidPdEndde(res.getInsptValidPdEndde()) + .chckValidPdBgnde(res.getChckValidPdBgnde()) + .chckValidPdEndde(res.getChckValidPdEndde()) + .registReqstSeNm(res.getRegistReqstSeNm()) + .frstRegistRqrcno(res.getFrstRegistRqrcno()) + .nmplCsdyRemnrDe(res.getNmplCsdyRemnrDe()) + .nmplCsdyAt(res.getNmplCsdyAt()) + .bssUsePd(res.getBssUsePd()) + .octhtErsrPrvntcNticeDe(res.getOcthtErsrPrvntcNticeDe()) + .ersrRegistDe(res.getErsrRegistDe()) + .ersrRegistSeCode(res.getErsrRegistSeCode()) + .ersrRegistSeNm(res.getErsrRegistSeNm()) + .mrtgcnt(res.getMrtgcnt()) + .vhclecnt(res.getVhclecnt()) + .stmdcnt(res.getStmdcnt()) + .adres1(res.getAdres1()) + .adresNm1(res.getAdresNm1()) + .adres(res.getAdres()) + .adresNm(res.getAdresNm()) + .indvdlBsnmAt(res.getIndvdlBsnmAt()) + .telno(res.getTelno()) + .mberNm(res.getMberNm()) + .mberSeCode(res.getMberSeCode()) + .mberSeNo(res.getMberSeNo()) + .taxxmptTrgterSeCode(res.getTaxxmptTrgterSeCode()) + .taxxmptTrgterSeCodeNm(res.getTaxxmptTrgterSeCodeNm()) + .cntMatter(res.getCntMatter()) + .emdNm(res.getEmdNm()) + .prvntccnt(res.getPrvntccnt()) + .xportFlflAtSttemntDe(res.getXportFlflAtSttemntDe()) + .partnRqrcno(res.getPartnRqrcno()) + .build(); + } + + // PK + private String carLedgerFrmbkId; + + // 요청(헤더/본문) 정보 + private String infoSysId; + private String infoSysIp; + private String sigunguCode; + private String cntcInfoCode; + private String chargerId; + private String chargerIp; + private String chargerNm; + + // 요청 본문 + private String dmndVhrno; + private String dmndOnesInformationOpen; + private String dmndCpttrNm; + private String dmndCpttrIhidnum; + private String dmndCpttrLegaldongCode; + private String dmndRouteSeCode; + private String dmndDetailExpression; + private String dmndInqireSeCode; + + // 응답 요약 정보 + private String cntcResultCode; + private String cntcResultDtls; + + // 응답 본문(마스터) + private String ledgerGroupNo; + private String ledgerIndvdlzNo; + private String vhmno; + private String vhrno; + private String vin; + private String vhctyAsortCode; + private String vhctyAsortNm; + private String cnm; + private String colorCode; + private String colorNm; + private String nmplStndrdCode; + private String nmplStndrdNm; + private String prposSeCode; + private String prposSeNm; + private String mtrsFomNm; + private String fomNm; + private String acqsAmount; + private String registDetailCode; + private String registDetailNm; + private String frstRegistDe; + private String caagEndde; + private String prye; + private String spmnno1; + private String spmnno2; + private String yblMd; + private String trvlDstnc; + private String insptValidPdBgnde; + private String insptValidPdEndde; + private String chckValidPdBgnde; + private String chckValidPdEndde; + private String registReqstSeNm; + private String frstRegistRqrcno; + private String nmplCsdyRemnrDe; + private String nmplCsdyAt; + private String bssUsePd; + private String octhtErsrPrvntcNticeDe; + private String ersrRegistDe; + private String ersrRegistSeCode; + private String ersrRegistSeNm; + private String mrtgcnt; + private String vhclecnt; + private String stmdcnt; + private String adres1; + private String adresNm1; + private String adres; + private String adresNm; + private String indvdlBsnmAt; + private String telno; + private String mberNm; + private String mberSeCode; + private String mberSeNo; + private String taxxmptTrgterSeCode; + private String taxxmptTrgterSeCodeNm; + private String cntMatter; + private String emdNm; + private String prvntccnt; + private String xportFlflAtSttemntDe; + private String partnRqrcno; + private String frstTrnsfrDe; + private String processImprtyResnCode; + private String processImprtyResnDtls; + + // 감사 + private String rgtr; +} diff --git a/src/main/java/com/vmis/interfaceapp/service/CarBassMatterInqireLogService.java b/src/main/java/com/vmis/interfaceapp/service/CarBassMatterInqireLogService.java new file mode 100644 index 0000000..2b621e4 --- /dev/null +++ b/src/main/java/com/vmis/interfaceapp/service/CarBassMatterInqireLogService.java @@ -0,0 +1,56 @@ +package com.vmis.interfaceapp.service; + +import com.vmis.interfaceapp.mapper.CarBassMatterInqireMapper; +import com.vmis.interfaceapp.model.basic.CarBassMatterInqireVO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +/** + * 자동차 기본사항 조회 로그 전용 서비스. + * + *

로그 적재만 별도 트랜잭션(REQUIRES_NEW)으로 처리하여, + * 외부 호출 실패나 상위 트랜잭션 롤백 상황에서도 로그는 영속화되도록 보장한다.

+ */ +@Slf4j +@Service +@RequiredArgsConstructor +public class CarBassMatterInqireLogService { + + private final CarBassMatterInqireMapper carBassMatterInqireMapper; + + /** + * 최초 API 요청 정보를 등록한다. (REQUIRES_NEW) + * @param request 요청 정보 + * @return 생성된 ID + */ + @Transactional(propagation = Propagation.REQUIRES_NEW) + public String createInitialRequestNewTx(CarBassMatterInqireVO request) { + String generatedId = carBassMatterInqireMapper.selectNextCarBassMatterInqireId(); + request.setCarBassMatterInqire(generatedId); + int result = carBassMatterInqireMapper.insertCarBassMatterInqire(request); + if (result != 1) { + throw new RuntimeException("자동차 기본 사항 조회 정보 등록 실패"); + } + log.info("[BASIC-REQ-LOG] 요청 정보 저장 완료(별도TX) - ID: {}, 차량번호: {}", generatedId, request.getDmndVhrno()); + return generatedId; + } + + /** + * 응답/에러 결과를 업데이트한다. (REQUIRES_NEW) + * @param response 업데이트 내용 + */ + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void updateResponseNewTx(CarBassMatterInqireVO response) { + if (response.getCarBassMatterInqire() == null) { + throw new IllegalArgumentException("자동차 기본 사항 조회 ID는 필수입니다."); + } + int result = carBassMatterInqireMapper.updateCarBassMatterInqire(response); + if (result != 1) { + throw new RuntimeException("자동차 기본 사항 조회 정보 업데이트 실패 - ID: " + response.getCarBassMatterInqire()); + } + log.info("[BASIC-RES-LOG] 응답/에러 정보 저장 완료(별도TX) - ID: {}, 결과코드: {}", response.getCarBassMatterInqire(), response.getCntcResultCode()); + } +} diff --git a/src/main/java/com/vmis/interfaceapp/service/CarBassMatterInqireService.java b/src/main/java/com/vmis/interfaceapp/service/CarBassMatterInqireService.java index f69f410..bd6f300 100644 --- a/src/main/java/com/vmis/interfaceapp/service/CarBassMatterInqireService.java +++ b/src/main/java/com/vmis/interfaceapp/service/CarBassMatterInqireService.java @@ -1,9 +1,14 @@ package com.vmis.interfaceapp.service; -import com.vmis.interfaceapp.mapper.CarBassMatterInqireMapper; +import com.vmis.interfaceapp.client.GovernmentApi; +import com.vmis.interfaceapp.util.ExceptionDetailUtil; +import com.vmis.interfaceapp.model.basic.BasicRequest; +import com.vmis.interfaceapp.model.basic.BasicResponse; import com.vmis.interfaceapp.model.basic.CarBassMatterInqireVO; +import com.vmis.interfaceapp.model.common.Envelope; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -21,106 +26,61 @@ import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor public class CarBassMatterInqireService { - private final CarBassMatterInqireMapper carBassMatterInqireMapper; + private final GovernmentApi governmentApi; + private final RequestEnricher enricher; + private final CarBassMatterInqireLogService logService; - /** - * 최초 API 요청 정보를 등록합니다. - * - *

시퀀스로 새로운 ID를 생성하여 요청 정보를 저장합니다. - * 트랜잭션 내에서 실행되며, 예외 발생 시 롤백됩니다.

- * - *

사용 예시:

- *
-     * {@code
-     * CarBassMatterInqire request = CarBassMatterInqire.builder()
-     *     .infoSysId("41-345")
-     *     .infoSysIp("105.19.10.135")
-     *     .sigunguCode("41460")
-     *     .cntcInfoCode("AC1_FD11_01")
-     *     .dmndVhrno("12가3456")
-     *     .rgtr("SYSTEM")
-     *     .build();
-     *
-     * String generatedId = service.createInitialRequest(request);
-     * log.info("생성된 ID: {}", generatedId);
-     * }
-     * 
- * - * @param request 요청 정보 (ID 제외) - * @return 생성된 자동차 기본 사항 조회 ID (CBMI000000000001 형식) - * @throws RuntimeException 저장 실패 시 - */ - @Transactional - public String createInitialRequest(CarBassMatterInqireVO request) { - // 시퀀스로 새로운 ID 생성 - String generatedId = carBassMatterInqireMapper.selectNextCarBassMatterInqireId(); - log.debug("생성된 자동차 기본 사항 조회 ID: {}", generatedId); - // 생성된 ID 설정 - request.setCarBassMatterInqire(generatedId); - // INSERT - int result = carBassMatterInqireMapper.insertCarBassMatterInqire(request); - if (result != 1) { - throw new RuntimeException("자동차 기본 사항 조회 정보 등록 실패"); - } - - log.info("자동차 기본 사항 조회 정보 등록 완료 - ID: {}, 차량번호: {}, 차대번호: {}", - generatedId, request.getDmndVhrno(), request.getDmndVin()); - - return generatedId; - } /** - * API 응답 결과를 업데이트합니다. - * - *

기존 레코드에 응답 데이터를 업데이트합니다. - * null이 아닌 필드만 업데이트되며, 트랜잭션 내에서 실행됩니다.

- * - *

사용 예시:

- *
-     * {@code
-     * CarBassMatterInqire response = CarBassMatterInqire.builder()
-     *     .carBassMatterInqire("CBMI000000000001")
-     *     .cntcResultCode("00")
-     *     .cntcResultDtls("성공")
-     *     .vhclno("12가3456")
-     *     .vin("KMHXX00XXXX000000")
-     *     .cnm("소나타")
-     *     // ... 기타 응답 필드
-     *     .build();
-     *
-     * service.updateResponse(response);
-     * }
-     * 
- * - * @param response 응답 정보 (carBassMatterInqire 필드 필수) - * @throws RuntimeException 업데이트 실패 시 (레코드가 없는 경우 포함) + * 자동차 기본사항 조회: 보강 -> 최초요청로그 -> 외부호출 -> 응답로그. */ @Transactional - public void updateResponse(CarBassMatterInqireVO response) { - if (response.getCarBassMatterInqire() == null) { - throw new IllegalArgumentException("자동차 기본 사항 조회 ID는 필수입니다."); - } + public ResponseEntity> basic(Envelope envelope) { + // 1) 요청 보강 + enricher.enrichBasic(envelope); - int result = carBassMatterInqireMapper.updateCarBassMatterInqire(response); - if (result != 1) { - throw new RuntimeException("자동차 기본 사항 조회 정보 업데이트 실패 - ID: " + response.getCarBassMatterInqire()); - } + String generatedId = null; + try { + // 2) 최초 요청 로그 저장 (첫 번째 데이터 기준) + if (envelope.getData() != null && !envelope.getData().isEmpty()) { + BasicRequest req = envelope.getData().get(0); + CarBassMatterInqireVO logEntity = CarBassMatterInqireVO.fromRequest(req); + generatedId = logService.createInitialRequestNewTx(logEntity); + } - log.info("자동차 기본 사항 조회 정보 업데이트 완료 - ID: {}, 결과코드: {}, 차량번호: {}", - response.getCarBassMatterInqire(), response.getCntcResultCode(), response.getVhclno()); - } + // 3) 외부 API 호출 + ResponseEntity> response = governmentApi.callBasic(envelope); - /** - * ID로 조회 정보를 조회합니다. - * - * @param carBassMatterInqire 자동차 기본 사항 조회 ID - * @return 조회된 정보 (없으면 null) - */ - @Transactional(readOnly = true) - public CarBassMatterInqireVO getById(String carBassMatterInqire) { - return carBassMatterInqireMapper.selectCarBassMatterInqireById(carBassMatterInqire); + // 4) 응답 로그 업데이트 + // 원본 소스, 정상적인 호출, 리턴(에러 리턴포함) 일 경우에만 에러 로그 남김 + if (generatedId != null && response.getBody() != null) { + CarBassMatterInqireVO update = CarBassMatterInqireVO.fromResponse(generatedId, response.getBody()); + if (update != null) { + logService.updateResponseNewTx(update); + } + } + + return response; + } catch (Exception e) { + // 5) 오류 로그 업데이트 + if (generatedId != null) { + try { + String detail = ExceptionDetailUtil.buildForLog(e); + CarBassMatterInqireVO errorLog = CarBassMatterInqireVO.builder() + .carBassMatterInqire(generatedId) // 자동차기본사항조회 ID (PK) + .cntcResultCode("99") // 연계결과코드 (99: 에러) + .cntcResultDtls(detail) // 연계결과상세 (에러 메시지) + .build(); + logService.updateResponseNewTx(errorLog); + log.error("[BASIC-ERR-LOG] API 호출 에러 정보 저장 완료(별도TX) - ID: {}, detail: {}", generatedId, detail, e); + } catch (Exception ignore) { + log.error("[BASIC-ERR-LOG] 에러 로그 저장 실패 - ID: {}", generatedId, ignore); + } + } + throw e; + } } } diff --git a/src/main/java/com/vmis/interfaceapp/service/CarLedgerFrmbkLogService.java b/src/main/java/com/vmis/interfaceapp/service/CarLedgerFrmbkLogService.java new file mode 100644 index 0000000..b840c70 --- /dev/null +++ b/src/main/java/com/vmis/interfaceapp/service/CarLedgerFrmbkLogService.java @@ -0,0 +1,61 @@ +package com.vmis.interfaceapp.service; + +import com.vmis.interfaceapp.mapper.CarLedgerFrmbkMapper; +import com.vmis.interfaceapp.model.ledger.CarLedgerFrmbkDtlVO; +import com.vmis.interfaceapp.model.ledger.CarLedgerFrmbkVO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * 자동차 등록 원부(갑) 로그 전용 서비스. + * - 모든 로깅(write)은 별도 트랜잭션(REQUIRES_NEW)으로 수행하여 상위 트랜잭션 롤백의 영향을 받지 않도록 한다. + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class CarLedgerFrmbkLogService { + + private final CarLedgerFrmbkMapper mapper; + + @Transactional(propagation = Propagation.REQUIRES_NEW) + public String createInitialRequestNewTx(CarLedgerFrmbkVO request) { + String id = mapper.selectNextCarLedgerFrmbkId(); + request.setCarLedgerFrmbkId(id); + int result = mapper.insertCarLedgerFrmbk(request); + if (result != 1) { + throw new RuntimeException("자동차 등록 원부(갑) 최초요청 등록 실패"); + } + log.info("[LEDGER-REQ-LOG] 최초 요청 저장(별도TX) - ID: {}, 차량번호: {}", id, request.getDmndVhrno()); + return id; + } + + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void updateResponseNewTx(CarLedgerFrmbkVO response) { + if (response.getCarLedgerFrmbkId() == null) { + throw new IllegalArgumentException("자동차 등록 원부(갑) ID는 필수입니다."); + } + int updated = mapper.updateCarLedgerFrmbk(response); + if (updated != 1) { + throw new RuntimeException("자동차 등록 원부(갑) 정보 업데이트 실패 - ID: " + response.getCarLedgerFrmbkId()); + } + log.info("[LEDGER-RES-LOG] 마스터 응답 업데이트(별도TX) - ID: {}, 결과코드: {}", + response.getCarLedgerFrmbkId(), response.getCntcResultCode()); + } + + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void saveDetailsNewTx(String masterId, List details) { + if (details == null || details.isEmpty()) return; + for (CarLedgerFrmbkDtlVO dtl : details) { + String dtlId = mapper.selectNextCarLedgerFrmbkDtlId(); + dtl.setCarLedgerFrmbkDtlId(dtlId); + dtl.setCarLedgerFrmbkId(masterId); + mapper.insertCarLedgerFrmbkDtl(dtl); + } + log.info("[LEDGER-RES-LOG] 상세 {}건 저장(별도TX) - ID: {}", details.size(), masterId); + } +} diff --git a/src/main/java/com/vmis/interfaceapp/service/CarLedgerFrmbkService.java b/src/main/java/com/vmis/interfaceapp/service/CarLedgerFrmbkService.java new file mode 100644 index 0000000..ab04306 --- /dev/null +++ b/src/main/java/com/vmis/interfaceapp/service/CarLedgerFrmbkService.java @@ -0,0 +1,84 @@ +package com.vmis.interfaceapp.service; + +import com.vmis.interfaceapp.client.GovernmentApi; +import com.vmis.interfaceapp.model.common.Envelope; +import com.vmis.interfaceapp.model.ledger.CarLedgerFrmbkDtlVO; +import com.vmis.interfaceapp.model.ledger.CarLedgerFrmbkVO; +import com.vmis.interfaceapp.model.ledger.LedgerRequest; +import com.vmis.interfaceapp.model.ledger.LedgerResponse; +import com.vmis.interfaceapp.util.ExceptionDetailUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * 자동차 등록 원부(갑) 서비스 (오케스트레이션) + * - 요청 보강, 외부 API 호출, 로그 서비스 위임 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class CarLedgerFrmbkService { + + private final GovernmentApi governmentApi; + private final RequestEnricher enricher; + private final CarLedgerFrmbkLogService logService; + + /** + * 자동차 등록원부(갑) 조회: 보강 -> 최초요청로그(별도TX) -> 외부호출 -> 응답로그(마스터/상세, 별도TX) -> 오류 시 에러로그(별도TX). + */ + @Transactional + public ResponseEntity> ledger(Envelope envelope) { + // 1) 요청 보강 + enricher.enrichLedger(envelope); + + String generatedId = null; + try { + // 2) 최초 요청 로그 저장 (첫 번째 데이터 기준) + if (envelope.getData() != null && !envelope.getData().isEmpty()) { + LedgerRequest req = envelope.getData().get(0); + CarLedgerFrmbkVO init = CarLedgerFrmbkVO.fromRequest(req); + generatedId = logService.createInitialRequestNewTx(init); + } + + // 3) 외부 API 호출 + ResponseEntity> response = governmentApi.callLedger(envelope); + + // 4) 응답 로그 업데이트 (마스터 + 상세) + if (generatedId != null && response.getBody() != null && + response.getBody().getData() != null && !response.getBody().getData().isEmpty()) { + LedgerResponse body = response.getBody().getData().get(0); + CarLedgerFrmbkVO masterUpdate = CarLedgerFrmbkVO.fromResponseMaster(generatedId, body); + logService.updateResponseNewTx(masterUpdate); + + List details = CarLedgerFrmbkDtlVO.listFromResponse(body, generatedId); + if (details != null && !details.isEmpty()) { + logService.saveDetailsNewTx(generatedId, details); + } + } + + return response; + } catch (Exception e) { + // 5) 오류 로그 업데이트 + if (generatedId != null) { + try { + String detail = ExceptionDetailUtil.buildForLog(e); + CarLedgerFrmbkVO errorLog = CarLedgerFrmbkVO.builder() + .carLedgerFrmbkId(generatedId) + .cntcResultCode("99") + .cntcResultDtls(detail) + .build(); + logService.updateResponseNewTx(errorLog); + log.error("[LEDGER-ERR-LOG] API 호출 에러 정보 저장 완료(별도TX) - ID: {}, detail: {}", generatedId, detail, e); + } catch (Exception ignore) { + log.error("[LEDGER-ERR-LOG] 에러 로그 저장 실패 - ID: {}", generatedId, ignore); + } + } + throw e; + } + } +} diff --git a/src/main/java/com/vmis/interfaceapp/service/VehicleInterfaceService.java b/src/main/java/com/vmis/interfaceapp/service/VehicleInterfaceService.java deleted file mode 100644 index 1987d75..0000000 --- a/src/main/java/com/vmis/interfaceapp/service/VehicleInterfaceService.java +++ /dev/null @@ -1,228 +0,0 @@ -package com.vmis.interfaceapp.service; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.vmis.interfaceapp.client.GovernmentApi; -import com.vmis.interfaceapp.model.basic.BasicRequest; -import com.vmis.interfaceapp.model.basic.BasicResponse; -import com.vmis.interfaceapp.model.basic.CarBassMatterInqireVO; -import com.vmis.interfaceapp.model.common.Envelope; -import com.vmis.interfaceapp.model.ledger.LedgerRequest; -import com.vmis.interfaceapp.model.ledger.LedgerResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -/** - * 차량 연계 서비스: 컨트롤러-클라이언트 사이의 오케스트레이션 계층. - * - 요청 보강(RequestEnricher) - * - 요청/응답 DB 로깅(CarBassMatterInqire) - * - 정부 API 클라이언트 위임(GovernmentApi) - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class VehicleInterfaceService { - - private final GovernmentApi governmentApi; - private final RequestEnricher enricher; - private final CarBassMatterInqireService carBassMatterInqireService; - - /** - * 자동차 기본사항 조회: 보강 -> 최초요청로그 -> 외부호출 -> 응답로그. - */ - @Transactional - public ResponseEntity> basic(Envelope envelope) { - // 1) 요청 보강 - enricher.enrichBasic(envelope); - - String generatedId = null; - try { - // 2) 최초 요청 로그 저장 (첫 번째 데이터 기준) - if (envelope.getData() != null && !envelope.getData().isEmpty()) { - BasicRequest req = envelope.getData().get(0); - CarBassMatterInqireVO logEntity = mapInitialLog(req); - generatedId = carBassMatterInqireService.createInitialRequest(logEntity); - log.info("[BASIC-REQ-LOG] 요청 정보 저장 완료 - ID: {}, 차량번호: {}", generatedId, req.getVhrno()); - } - - // 3) 외부 API 호출 - ResponseEntity> response = governmentApi.callBasic(envelope); - - // 4) 응답 로그 업데이트 - // 원본 소스, 정상적인 호출, 리턴(에러 리턴포함) 일 경우에만 에러 로그 남김 - if (generatedId != null && response.getBody() != null) { - CarBassMatterInqireVO update = mapResponseLog(generatedId, response.getBody()); - if (update != null) { - carBassMatterInqireService.updateResponse(update); - log.info("[BASIC-RES-LOG] 응답 정보 저장 완료 - ID: {}", generatedId); - } - } - - return response; - } catch (Exception e) { - // 5) 오류 로그 업데이트 - if (generatedId != null) { - try { - String detail = buildExceptionDetail(e); - CarBassMatterInqireVO errorLog = CarBassMatterInqireVO.builder() - .carBassMatterInqire(generatedId) - .cntcResultCode("99") - .cntcResultDtls(detail) - .build(); - carBassMatterInqireService.updateResponse(errorLog); - log.error("[BASIC-ERR-LOG] API 호출 에러 정보 저장 완료 - ID: {}, detail: {}", generatedId, detail, e); - } catch (Exception ignore) { - log.error("[BASIC-ERR-LOG] 에러 로그 저장 실패 - ID: {}", generatedId, ignore); - } - } - throw e; - } - } - - /** - * 자동차 등록원부(갑) 조회: 보강 -> 외부호출. (별도 로그 테이블 미정) - */ - public ResponseEntity> ledger(Envelope envelope) { - enricher.enrichLedger(envelope); - return governmentApi.callLedger(envelope); - } - - private CarBassMatterInqireVO mapInitialLog(BasicRequest request) { - return CarBassMatterInqireVO.builder() - .infoSysId(request.getInfoSysId()) - .infoSysIp(request.getInfoSysIp()) - .sigunguCode(request.getSigunguCode()) - .cntcInfoCode(request.getCntcInfoCode()) - .chargerId(request.getChargerId()) - .chargerIp(request.getChargerIp()) - .chargerNm(request.getChargerNm()) - .dmndLevyStdde(request.getLevyStdde()) - .dmndInqireSeCode(request.getInqireSeCode()) - .dmndVhrno(request.getVhrno()) - .dmndVin(request.getVin()) - .rgtr("SYSTEM") - .build(); - } - - private CarBassMatterInqireVO mapResponseLog(String id, Envelope envelope) { - if (envelope.getData() == null || envelope.getData().isEmpty()) return null; - BasicResponse response = envelope.getData().get(0); - CarBassMatterInqireVO.CarBassMatterInqireVOBuilder builder = CarBassMatterInqireVO.builder() - .carBassMatterInqire(id) - .cntcResultCode(response.getCntcResultCode()) - .cntcResultDtls(response.getCntcResultDtls()); - - if (response.getRecord() != null && !response.getRecord().isEmpty()) { - BasicResponse.Record record = response.getRecord().get(0); - mapRecordToEntity(builder, record); - } - return builder.build(); - } - - private void mapRecordToEntity(CarBassMatterInqireVO.CarBassMatterInqireVOBuilder builder, BasicResponse.Record record) { - builder - .prye(record.getPrye()) - .registDe(record.getRegistDe()) - .ersrRegistSeCode(record.getErsrRegistSeCode()) - .ersrRegistSeNm(record.getErsrRegistSeNm()) - .ersrRegistDe(record.getErsrRegistDe()) - .registDetailCode(record.getRegistDetailCode()) - .dsplvl(record.getDsplvl()) - .useStrnghldLegaldongCode(record.getUseStrnghldLegaldongCode()) - .useStrnghldAdstrdCode(record.getUseStrnghldAdstrdCode()) - .useStrnghldMntn(record.getUseStrnghldMntn()) - .useStrnghldLnbr(record.getUseStrnghldLnbr()) - .useStrnghldHo(record.getUseStrnghldHo()) - .useStrnghldAdresNm(record.getUseStrnghldAdresNm()) - .useStrnghldRoadNmCode(record.getUseStrnghldRoadNmCode()) - .usgsrhldUndgrndBuldSeCode(record.getUsgsrhldUndgrndBuldSeCode()) - .useStrnghldBuldMainNo(record.getUseStrnghldBuldMainNo()) - .useStrnghldBuldSubNo(record.getUseStrnghldBuldSubNo()) - .usgsrhldAdresFull(record.getUsgsrhldAdresFull()) - .mberSeCode(record.getMberSeCode()) - .mberSeNo(record.getMberSeNo()) - .mberNm(record.getMberNm()) - .telno(record.getTelno()) - .ownerLegaldongCode(record.getOwnerLegaldongCode()) - .ownerAdstrdCode(record.getOwnerAdstrdCode()) - .ownerMntn(record.getOwnerMntn()) - .ownerLnbr(record.getOwnerLnbr()) - .ownerHo(record.getOwnerHo()) - .ownerAdresNm(record.getOwnerAdresNm()) - .ownerRoadNmCode(record.getOwnerRoadNmCode()) - .ownerUndgrndBuldSeCode(record.getOwnerUndgrndBuldSeCode()) - .ownerBuldMainNo(record.getOwnerBuldMainNo()) - .ownerBuldSubNo(record.getOwnerBuldSubNo()) - .ownrWholaddr(record.getOwnerAdresFull()) - .aftrVhrno(record.getAftrVhrno()) - .useFuelCode(record.getUseFuelCode()) - .prposSeCode(record.getPrposSeCode()) - .mtrsFomNm(record.getMtrsFomNm()) - .frntVhrno(record.getFrntVhrno()) - .vhclno(record.getVhrno()) - .vin(record.getVin()) - .cnm(record.getCnm()) - .vhcleTotWt(record.getVhcleTotWt()) - .caagEndde(record.getCaagEndde()) - .changeDe(record.getChangeDe()) - .vhctyAsortCode(record.getVhctyAsortCode()) - .vhctyTyCode(record.getVhctyTyCode()) - .vhctySeCode(record.getVhctySeCode()) - .mxmmLdg(record.getMxmmLdg()) - .vhctyAsortNm(record.getVhctyAsortNm()) - .vhctyTyNm(record.getVhctyTyNm()) - .vhctySeNm(record.getVhctySeNm()) - .frstRegistDe(record.getFrstRegistDe()) - .fomNm(record.getFomNm()) - .acqsDe(record.getAcqsDe()) - .acqsEndDe(record.getAcqsEndDe()) - .yblMd(record.getYblMd()) - .transrRegistDe(record.getTransrRegistDe()) - .spcfRegistSttusCode(record.getSpcfRegistSttusCode()) - .colorNm(record.getColorNm()) - .mrtgCo(record.getMrtgCo()) - .seizrCo(record.getSeizrCo()) - .stmdCo(record.getStmdCo()) - .nmplCsdyAt(record.getNmplCsdyAt()) - .nmplCsdyRemnrDe(record.getNmplCsdyRemnrDe()) - .originSeCode(record.getOriginSeCode()) - .nmplStndrdCode(record.getNmplStndrdCode()) - .acqsAmount(record.getAcqsAmount()) - .insptValidPdBgnde(record.getInsptValidPdBgnde()) - .insptValidPdEndde(record.getInsptValidPdEndde()) - .useStrnghldGrcCode(record.getUseStrnghldGrcCode()) - .tkcarPscapCo(record.getTkcarPscapCo()) - .spmnno(record.getSpmnno()) - .trvlDstnc(record.getTrvlDstnc()) - .frstRegistRqrcno(record.getFrstRegistRqrcno()) - .vlntErsrPrvntcNticeDe(record.getVlntErsrPrvntcNticeDe()) - .registInsttNm(record.getRegistInsttNm()) - .processImprtyResnCode(record.getProcessImprtyResnCode()) - .processImprtyResnDtls(record.getProcessImprtyResnDtls()) - .cbdLt(record.getCbdLt()) - .cbdBt(record.getCbdBt()) - .cbdHg(record.getCbdHg()) - .frstMxmmLdg(record.getFrstMxmmLdg()) - .fuelCnsmpRt(record.getFuelCnsmpRt()) - .elctyCmpndFuelCnsmpRt(record.getElctyCmpndFuelCnsmpRt()); - } - - private String buildExceptionDetail(Throwable t) { - if (t == null) return "오류: unknown"; - Throwable root = t; - int guard = 0; - while (root.getCause() != null && root.getCause() != root && guard++ < 20) { - root = root.getCause(); - } - String className = root.getClass().getName(); - String message = root.getMessage(); - String detail = (message != null && !message.isEmpty()) ? (className + ": " + message) : root.toString(); - // DB 컬럼(CNTC_RESULT_DTLS) 길이: 200 - if (detail.length() > 200) { - detail = detail.substring(0, 200); - } - return detail; - } -} diff --git a/src/main/java/com/vmis/interfaceapp/util/ExceptionDetailUtil.java b/src/main/java/com/vmis/interfaceapp/util/ExceptionDetailUtil.java new file mode 100644 index 0000000..0d5a8b0 --- /dev/null +++ b/src/main/java/com/vmis/interfaceapp/util/ExceptionDetailUtil.java @@ -0,0 +1,29 @@ +package com.vmis.interfaceapp.util; + +/** + * Common helper to extract root-cause message and truncate to DB column limit (default 4000 chars). + */ +public final class ExceptionDetailUtil { + + private ExceptionDetailUtil() {} + + public static String buildForLog(Throwable t) { + return buildForLog(t, 4000); + } + + public static String buildForLog(Throwable t, int maxLen) { + if (t == null) return "오류: unknown"; + Throwable root = t; + int guard = 0; + while (root.getCause() != null && root.getCause() != root && guard++ < 20) { + root = root.getCause(); + } + String className = root.getClass().getName(); + String message = root.getMessage(); + String detail = (message != null && !message.isEmpty()) ? (className + ": " + message) : root.toString(); + if (detail != null && detail.length() > maxLen) { + detail = detail.substring(0, maxLen); + } + return detail; + } +} diff --git a/src/main/resources/mybatis/mapper/CarLedgerFrmbkMapper_maria.xml b/src/main/resources/mybatis/mapper/CarLedgerFrmbkMapper_maria.xml new file mode 100644 index 0000000..4474790 --- /dev/null +++ b/src/main/resources/mybatis/mapper/CarLedgerFrmbkMapper_maria.xml @@ -0,0 +1,178 @@ + + + + + + + + + + + + + INSERT INTO tb_car_ledger_frmbk ( + CAR_LEDGER_FRMBK_ID, + INFO_SYS_ID, + INFO_SYS_IP, + SIGUNGU_CODE, + CNTC_INFO_CODE, + CHARGER_ID, + CHARGER_IP, + CHARGER_NM, + DMND_VHRNO, + DMND_ONES_INFORMATION_OPEN, + DMND_CPTTR_NM, + DMND_CPTTR_IHIDNUM, + DMND_CPTTR_LEGALDONG_CODE, + DMND_ROUTE_SE_CODE, + DMND_DETAIL_EXPRESSION, + DMND_INQIRE_SE_CODE, + REG_DT, + RGTR + ) VALUES ( + #{carLedgerFrmbkId}, + #{infoSysId}, + #{infoSysIp}, + #{sigunguCode}, + #{cntcInfoCode}, + #{chargerId}, + #{chargerIp}, + #{chargerNm}, + #{dmndVhrno}, + #{dmndOnesInformationOpen}, + #{dmndCpttrNm}, + #{dmndCpttrIhidnum}, + #{dmndCpttrLegaldongCode}, + #{dmndRouteSeCode}, + #{dmndDetailExpression}, + #{dmndInqireSeCode}, + NOW(), + #{rgtr} + ) + + + + + UPDATE tb_car_ledger_frmbk + + CNTC_RESULT_CODE = #{cntcResultCode}, + CNTC_RESULT_DTLS = #{cntcResultDtls}, + LEDGER_GROUP_NO = #{ledgerGroupNo}, + LEDGER_INDVDLZ_NO = #{ledgerIndvdlzNo}, + VHMNO = #{vhmno}, + VHRNO = #{vhrno}, + VIN = #{vin}, + VHCTY_ASORT_CODE = #{vhctyAsortCode}, + VHCTY_ASORT_NM = #{vhctyAsortNm}, + CNM = #{cnm}, + COLOR_CODE = #{colorCode}, + COLOR_NM = #{colorNm}, + NMPL_STNDRD_CODE = #{nmplStndrdCode}, + NMPL_STNDRD_NM = #{nmplStndrdNm}, + PRPOS_SE_CODE = #{prposSeCode}, + PRPOS_SE_NM = #{prposSeNm}, + MTRS_FOM_NM = #{mtrsFomNm}, + FOM_NM = #{fomNm}, + ACQS_AMOUNT = #{acqsAmount}, + REGIST_DETAIL_CODE = #{registDetailCode}, + REGIST_DETAIL_NM = #{registDetailNm}, + FRST_REGIST_DE = #{frstRegistDe}, + CAAG_ENDDE = #{caagEndde}, + PRYE = #{prye}, + SPMNNO1 = #{spmnno1}, + SPMNNO2 = #{spmnno2}, + YBL_MD = #{yblMd}, + TRVL_DSTNC = #{trvlDstnc}, + INSPT_VALID_PD_BGNDE = #{insptValidPdBgnde}, + INSPT_VALID_PD_ENDDE = #{insptValidPdEndde}, + CHCK_VALID_PD_BGNDE = #{chckValidPdBgnde}, + CHCK_VALID_PD_ENDDE = #{chckValidPdEndde}, + REGIST_REQST_SE_NM = #{registReqstSeNm}, + FRST_REGIST_RQRCNO = #{frstRegistRqrcno}, + NMPL_CSDY_REMNR_DE = #{nmplCsdyRemnrDe}, + NMPL_CSDY_AT = #{nmplCsdyAt}, + BSS_USE_PD = #{bssUsePd}, + OCTHT_ERSR_PRVNTC_NTICE_DE = #{octhtErsrPrvntcNticeDe}, + ERSR_REGIST_DE = #{ersrRegistDe}, + ERSR_REGIST_SE_CODE = #{ersrRegistSeCode}, + ERSR_REGIST_SE_NM = #{ersrRegistSeNm}, + MRTGCNT = #{mrtgcnt}, + VHCLECNT = #{vhclecnt}, + STMDCNT = #{stmdcnt}, + ADRES1 = #{adres1}, + ADRES_NM1 = #{adresNm1}, + ADRES = #{adres}, + ADRES_NM = #{adresNm}, + INDVDL_BSNM_AT = #{indvdlBsnmAt}, + TELNO = #{telno}, + MBER_NM = #{mberNm}, + MBER_SE_CODE = #{mberSeCode}, + MBER_SE_NO = #{mberSeNo}, + TAXXMPT_TRGTER_SE_CODE = #{taxxmptTrgterSeCode}, + TAXXMPT_TRGTER_SE_CODE_NM = #{taxxmptTrgterSeCodeNm}, + CNT_MATTER = #{cntMatter}, + EMD_NM = #{emdNm}, + PRVNTCCNT = #{prvntccnt}, + XPORT_FLFL_AT_STTEMNT_DE = #{xportFlflAtSttemntDe}, + PARTN_RQRCNO = #{partnRqrcno}, + FRST_TRNSFR_DE = #{frstTrnsfrDe}, + PROCESS_IMPRTY_RESN_CODE = #{processImprtyResnCode}, + PROCESS_IMPRTY_RESN_DTLS = #{processImprtyResnDtls}, + + WHERE CAR_LEDGER_FRMBK_ID = #{carLedgerFrmbkId} + + + + + INSERT INTO tb_car_ledger_frmbk_dtl ( + CAR_LEDGER_FRMBK_DTL_ID, + CAR_LEDGER_FRMBK_ID, + MAINCHK, + CHANGE_JOB_SE_CODE, + MAINNO, + SUBNO, + DTLS, + RQRCNO, + VHMNO, + LEDGER_GROUP_NO, + LEDGER_INDVDLZ_NO, + GUBUN_NM, + CHANGE_DE, + DETAIL_SN, + FLAG, + REG_DT, + RGTR + ) VALUES ( + #{carLedgerFrmbkDtlId}, + #{carLedgerFrmbkId}, + #{mainchk}, + #{changeJobSeCode}, + #{mainno}, + #{subno}, + #{dtls}, + #{rqrcno}, + #{vhmno}, + #{ledgerGroupNo}, + #{ledgerIndvdlzNo}, + #{gubunNm}, + #{changeDe}, + #{detailSn}, + #{flag}, + NOW(), + #{rgtr} + ) + + + + + +