전체 구성 수정중...

internalApi
박성영 4 weeks ago
parent 2459e07a3f
commit 8eeee86a46

@ -131,7 +131,7 @@ public class VehicleInterfaceController {
mediaType = MediaType.APPLICATION_JSON_VALUE,
examples = @ExampleObject(
name = "등록원부 조회 예제",
value = "{\"data\": [{\"VHRNO\": \"12가3456\",\"ONES_INFORMATION_OPEN\": \"1\",\"CPTTR_NM\": \"홍길동\",\"CPTTR_IHIDNUM\": \"8801011234567\",\"CPTTR_LEGALDONG_CODE\": \"1111011700\",\"ROUTE_SE_CODE\": \"3\",\"DETAIL_EXPRESSION\": \"1\",\"INQIRE_SE_CODE\": \"1\"}]}"
value = "{\"data\": [{\"VHRNO\": \"12가3456\",\"ONES_INFORMATION_OPEN\": \"1\",\"CPTTR_NM\": \"홍길동\",\"CPTTR_IHIDNUM\": \"8801011234567\",\"CPTTR_LEGALDONG_CODE\": \"1111011700\",\"DETAIL_EXPRESSION\": \"1\",\"INQIRE_SE_CODE\": \"1\"}]}"
)
)
)

@ -1,36 +0,0 @@
package go.kr.project.carInspectionPenalty.registration.comparison;
import go.kr.project.api.model.VehicleApiResponseVO;
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO;
import lombok.Builder;
import lombok.Getter;
/**
*
*/
@Getter
@Builder
public class ComparisonContext {
/**
*
*/
private final CarFfnlgTrgtVO existingData;
/**
* API
*/
private final VehicleApiResponseVO apiResponse;
/**
* ( ID)
*/
private final String userId;
/**
* ( )
*/
public String getVhclno() {
return existingData != null ? existingData.getVhclno() : null;
}
}

@ -1,50 +0,0 @@
package go.kr.project.carInspectionPenalty.registration.comparison;
import lombok.Builder;
import lombok.Getter;
/**
*
*/
@Getter
@Builder
public class ComparisonResult {
/**
*
* true: ( )
* false: ( )
*/
private final boolean applied;
/**
*
* 02=, 03=, 04=
*/
private final String statusCode;
/**
*
*/
private final String message;
/**
*
*/
public static ComparisonResult notApplied() {
return ComparisonResult.builder()
.applied(false)
.build();
}
/**
*
*/
public static ComparisonResult applied(String statusCode, String message) {
return ComparisonResult.builder()
.applied(true)
.statusCode(statusCode)
.message(message)
.build();
}
}

@ -1,42 +0,0 @@
package go.kr.project.carInspectionPenalty.registration.comparison;
/**
*
*
* <p>Chain of Responsibility .</p>
*
* <p> :</p>
* <ol>
* <li> </li>
* <li> DB ComparisonResult.applied() </li>
* <li> ComparisonResult.notApplied() </li>
* </ol>
*
* <p> applied=true , false .</p>
*/
public interface ComparisonRule {
/**
* .
*
* @param context
* @return (applied=true , false )
*/
ComparisonResult execute(ComparisonContext context);
/**
* . ( )
*
* @return
*/
String getRuleName();
/**
* . ( )
*
* @return (: 100)
*/
default int getOrder() {
return 100;
}
}

@ -1,90 +0,0 @@
package go.kr.project.carInspectionPenalty.registration.comparison;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
/**
*
*
* <p> .</p>
*
* <p> :</p>
* <ol>
* <li> getOrder() </li>
* <li> </li>
* <li> applied=true </li>
* <li> applied=false null </li>
* </ol>
*/
@Slf4j
@Component
public class ComparisonRuleProcessor {
private final List<ComparisonRule> rules;
/**
* ComparisonRule .
* Spring ComparisonRule .
*/
public ComparisonRuleProcessor(List<ComparisonRule> rules) {
// getOrder() 순서대로 정렬
this.rules = rules.stream()
.sorted(Comparator.comparingInt(ComparisonRule::getOrder))
.collect(Collectors.toList());
log.info("========== 비교 규칙 프로세서 초기화 ==========");
log.info("등록된 규칙 개수: {}", this.rules.size());
this.rules.forEach(rule ->
log.info(" - [순서: {}] {}", rule.getOrder(), rule.getRuleName())
);
log.info("============================================");
}
/**
* .
*
* @param context
* @return ( null)
*/
public ComparisonResult process(ComparisonContext context) {
String vhclno = context.getVhclno();
log.debug("========== 비교 규칙 체인 시작: {} ==========", vhclno);
for (ComparisonRule rule : rules) {
log.debug("[{}] 규칙 실행 중... 차량번호: {}", rule.getRuleName(), vhclno);
try {
ComparisonResult result = rule.execute(context);
if (result.isApplied()) {
log.info("[{}] 규칙 적용됨! 차량번호: {}, 상태코드: {}, 메시지: {}",
rule.getRuleName(), vhclno, result.getStatusCode(), result.getMessage());
log.debug("========== 비교 규칙 체인 종료 (규칙 적용): {} ==========", vhclno);
return result;
}
log.debug("[{}] 규칙 적용되지 않음. 다음 규칙으로 이동...", rule.getRuleName());
} catch (Exception e) {
log.error("[{}] 규칙 실행 중 오류 발생! 차량번호: {}", rule.getRuleName(), vhclno, e);
throw new RuntimeException(
String.format("[%s] 규칙 실행 중 오류 발생: %s - %s",
rule.getRuleName(), vhclno, e.getMessage()), e);
}
}
log.debug("========== 비교 규칙 체인 종료 (적용된 규칙 없음): {} ==========", vhclno);
return null; // 적용된 규칙이 없음
}
/**
* . ()
*/
public List<ComparisonRule> getRules() {
return rules;
}
}

@ -1,97 +0,0 @@
package go.kr.project.carInspectionPenalty.registration.comparison.rules;
import go.kr.project.carInspectionPenalty.registration.comparison.ComparisonContext;
import go.kr.project.carInspectionPenalty.registration.comparison.ComparisonResult;
import go.kr.project.carInspectionPenalty.registration.comparison.ComparisonRule;
import go.kr.project.carInspectionPenalty.registration.mapper.CarFfnlgTrgtMapper;
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
/**
*
*
* <p>(MBER_NM) "상품용" .</p>
*
* <p> :</p>
* <ul>
* <li> "상품용" </li>
* </ul>
*
* <p> :</p>
* <ul>
* <li>TASK_PRCS_STTS_CD = 02 ()</li>
* <li>TASK_PRCS_YMD = </li>
* <li>CAR_BSC_MTTR_INQ_FLNM = </li>
* </ul>
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class ProductUseComparisonRule implements ComparisonRule {
private static final String STATUS_CODE = "02"; // 상품용
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
private final CarFfnlgTrgtMapper mapper;
@Override
public ComparisonResult execute(ComparisonContext context) {
String vhclno = context.getVhclno();
// API 응답 데이터 유효성 검사
if (context.getApiResponse().getBasicInfo() == null ||
context.getApiResponse().getBasicInfo().getRecord() == null ||
context.getApiResponse().getBasicInfo().getRecord().isEmpty()) {
log.debug("[{}] API 응답 데이터가 없어 규칙을 적용할 수 없습니다. 차량번호: {}", getRuleName(), vhclno);
return ComparisonResult.notApplied();
}
// 대표소유자성명 추출
String mberNm = context.getApiResponse()
.getBasicInfo()
.getRecord()
.get(0)
.getMberNm();
// 상품용 체크
if (mberNm == null || !mberNm.contains("상품용")) {
log.debug("[{}] 상품용에 해당하지 않습니다. 차량번호: {}, 소유자명: {}", getRuleName(), vhclno, mberNm);
return ComparisonResult.notApplied();
}
log.info("[{}] 상품용 감지! 차량번호: {}, 소유자명: {}", getRuleName(), vhclno, mberNm);
// 업무 처리 상태 업데이트
CarFfnlgTrgtVO updateData = context.getExistingData();
updateData.setTaskPrcsSttsCd(STATUS_CODE);
updateData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
updateData.setCarBscMttrInqFlnm(mberNm); // 소유자명 저장
updateData.setCarBscMttrInqSggCd(null); // 이첩 필드는 null
updateData.setCarBscMttrInqSggNm(null);
int updateResult = mapper.update(updateData);
if (updateResult > 0) {
log.info("[{}] 처리 완료! 차량번호: {}", getRuleName(), vhclno);
return ComparisonResult.applied(STATUS_CODE, "상품용으로 처리되었습니다.");
} else {
log.error("[{}] 업데이트 실패! 차량번호: {}", getRuleName(), vhclno);
throw new RuntimeException(String.format("상품용 업데이트 실패: %s", vhclno));
}
}
@Override
public String getRuleName() {
return "상품용";
}
@Override
public int getOrder() {
return 10; // 상품용은 가장 먼저 체크 (낮은 순서)
}
}

@ -1,169 +0,0 @@
package go.kr.project.carInspectionPenalty.registration.comparison.rules;
import go.kr.project.carInspectionPenalty.registration.comparison.ComparisonContext;
import go.kr.project.carInspectionPenalty.registration.comparison.ComparisonResult;
import go.kr.project.carInspectionPenalty.registration.comparison.ComparisonRule;
import go.kr.project.carInspectionPenalty.registration.mapper.CarFfnlgTrgtMapper;
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO;
import go.kr.project.system.user.mapper.UserMapper;
import go.kr.project.system.user.model.SystemUserVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
/**
*
*
* <p> 4 4 .</p>
*
* <p> :</p>
* <ul>
* <li> (USE_STRNGHLD_LEGALDONG_CODE) 4</li>
* <li>(tb_user) ORG_CD 4</li>
* <li> </li>
* </ul>
*
* <p> :</p>
* <ul>
* <li>TASK_PRCS_STTS_CD = 03 ()</li>
* <li>TASK_PRCS_YMD = </li>
* <li>CAR_BSC_MTTR_INQ_SGG_CD = 5</li>
* <li>CAR_BSC_MTTR_INQ_SGG_NM = tb_sgg_cd </li>
* </ul>
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class TransferComparisonRule implements ComparisonRule {
private static final String STATUS_CODE = "03"; // 이첩
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
private final CarFfnlgTrgtMapper mapper;
private final UserMapper userMapper;
@Override
public ComparisonResult execute(ComparisonContext context) {
String vhclno = context.getVhclno();
// API 응답 데이터 유효성 검사
if (context.getApiResponse().getBasicInfo() == null ||
context.getApiResponse().getBasicInfo().getRecord() == null ||
context.getApiResponse().getBasicInfo().getRecord().isEmpty()) {
log.debug("[{}] API 응답 데이터가 없어 규칙을 적용할 수 없습니다. 차량번호: {}", getRuleName(), vhclno);
return ComparisonResult.notApplied();
}
go.kr.project.api.model.response.BasicResponse.Record basicInfo = context.getApiResponse().getBasicInfo().getRecord().get(0);
// ========== 이첩 조건들 (OR 로직: 하나라도 만족하면 이첩) ==========
// 조건1: 법정동코드 불일치
if (checkLegalDongCodeMismatch(context, basicInfo, vhclno)) {
return processTransfer(context, basicInfo, vhclno, "법정동코드 불일치");
}
// 조건2: 향후 추가될 이첩 조건들 (예시)
// if (checkOtherTransferCondition(context, basicInfo, vhclno)) {
// return processTransfer(context, basicInfo, vhclno, "다른 조건");
// }
// 모든 이첩 조건에 해당하지 않음
log.debug("[{}] 모든 이첩 조건에 해당하지 않습니다. 차량번호: {}", getRuleName(), vhclno);
return ComparisonResult.notApplied();
}
/**
* 1:
* 4 != 4
*/
private boolean checkLegalDongCodeMismatch(ComparisonContext context,
go.kr.project.api.model.response.BasicResponse.Record basicInfo,
String vhclno) {
// 사용본거지법정동코드 추출
String useStrnghldLegaldongCode = basicInfo.getUseStrnghldLegaldongCode();
if (useStrnghldLegaldongCode == null || useStrnghldLegaldongCode.length() < 4) {
log.debug("[{}][조건1] 법정동코드가 없거나 길이 부족. 차량번호: {}", getRuleName(), vhclno);
return false;
}
// 사용자 정보 조회
SystemUserVO userInfo = userMapper.selectUser(context.getUserId());
if (userInfo == null || userInfo.getOrgCd() == null) {
log.debug("[{}][조건1] 사용자 정보 없음. 차량번호: {}", getRuleName(), vhclno);
return false;
}
// 법정동코드 앞 4자리와 사용자 조직코드 앞 4자리 비교
String legalDong4 = useStrnghldLegaldongCode.substring(0, 4);
String userOrgCd = userInfo.getOrgCd();
String userOrg4 = userOrgCd.length() >= 4 ? userOrgCd.substring(0, 4) : userOrgCd;
if (legalDong4.equals(userOrg4)) {
log.debug("[{}][조건1] 법정동코드 일치. 차량번호: {}, 법정동: {}, 조직: {}",
getRuleName(), vhclno, legalDong4, userOrg4);
return false;
}
log.info("[{}][조건1] 법정동코드 불일치! 차량번호: {}, 법정동: {}, 조직: {}",
getRuleName(), vhclno, legalDong4, userOrg4);
return true;
}
/**
* ( )
*/
private ComparisonResult processTransfer(ComparisonContext context,
go.kr.project.api.model.response.BasicResponse.Record basicInfo,
String vhclno,
String reason) {
log.info("[{}] 이첩 감지! 차량번호: {}, 사유: {}", getRuleName(), vhclno, reason);
// 사용본거지법정동코드 추출
String useStrnghldLegaldongCode = basicInfo.getUseStrnghldLegaldongCode();
// 시군구 코드 (법정동코드 앞 5자리)
String sggCd = (useStrnghldLegaldongCode != null && useStrnghldLegaldongCode.length() >= 5)
? useStrnghldLegaldongCode.substring(0, 5)
: (useStrnghldLegaldongCode != null ? useStrnghldLegaldongCode : "");
// 시군구명 조회
String sggNm = mapper.selectSggNmBySggCd(sggCd);
if (sggNm == null || sggNm.isEmpty()) {
log.warn("[{}] 시군구명 조회 실패. 시군구코드: {} (빈 문자열로 처리)", getRuleName(), sggCd);
sggNm = ""; // 시군구명이 없어도 이첩 처리는 진행
}
// 업무 처리 상태 업데이트
CarFfnlgTrgtVO updateData = context.getExistingData();
updateData.setTaskPrcsSttsCd(STATUS_CODE);
updateData.setTaskPrcsYmd(LocalDate.now().format(DATE_FORMATTER));
updateData.setCarBscMttrInqFlnm(null); // 상품용 필드는 null
updateData.setCarBscMttrInqSggCd(sggCd); // 시군구 코드 저장
updateData.setCarBscMttrInqSggNm(sggNm); // 시군구명 저장
int updateResult = mapper.update(updateData);
if (updateResult > 0) {
log.info("[{}] 처리 완료! 차량번호: {}, 시군구: {}({})", getRuleName(), vhclno, sggNm, sggCd);
return ComparisonResult.applied(STATUS_CODE, String.format("이첩으로 처리되었습니다. (시군구: %s)", sggNm));
} else {
log.error("[{}] 업데이트 실패! 차량번호: {}", getRuleName(), vhclno);
throw new RuntimeException(String.format("이첩 업데이트 실패: %s", vhclno));
}
}
@Override
public String getRuleName() {
return "이첩";
}
@Override
public int getOrder() {
return 20; // 상품용 다음으로 체크
}
}

@ -4,9 +4,6 @@ import egovframework.exception.MessageException;
import go.kr.project.api.model.VehicleApiResponseVO;
import go.kr.project.api.model.request.BasicRequest;
import go.kr.project.api.service.VehicleInfoService;
import go.kr.project.carInspectionPenalty.registration.comparison.ComparisonContext;
import go.kr.project.carInspectionPenalty.registration.comparison.ComparisonResult;
import go.kr.project.carInspectionPenalty.registration.comparison.ComparisonRuleProcessor;
import go.kr.project.carInspectionPenalty.registration.config.CarFfnlgTxtParseConfig;
import go.kr.project.carInspectionPenalty.registration.mapper.CarFfnlgTrgtMapper;
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO;
@ -21,7 +18,6 @@ import org.springframework.web.multipart.MultipartFile;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
@ -29,7 +25,6 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Service
@ -43,11 +38,6 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
private final CarFfnlgTxtParseConfig parseConfig;
private final VehicleInfoService vehicleInfoService;
// ========== 비교 로직 선택 (둘 중 하나 선택) ==========
// 방법1: Chain of Responsibility 패턴 (복잡하지만 확장성 좋음)
private final ComparisonRuleProcessor comparisonRuleProcessor;
// 방법2: 일반 Service/Impl 패턴 (간단하고 명확함)
private final ComparisonService comparisonService;
// 날짜 형식 (YYYYMMDD)
@ -960,7 +950,6 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
BasicRequest apiRequest = new BasicRequest();
apiRequest.setVhrno(vhclno);
apiRequest.setLevyStdde(inspYmd != null ? inspYmd.replace("-", "") : "");
// INQIRE_SE_CODE는 VmisRequestEnricher에서 자동 설정됨 (VHRNO 있으면 "3")
VehicleApiResponseVO apiResponse = vehicleInfoService.getVehicleInfo(apiRequest);
@ -972,8 +961,10 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
continue;
}
// 3. 비교 로직 실행
String statusCode = executeComparisonLogic(existingData, apiResponse, rgtr);
String statusCode = comparisonService.executeComparison(existingData, apiResponse, rgtr);
// 결과 처리
if (statusCode != null) {
@ -1032,44 +1023,4 @@ public class CarFfnlgTrgtServiceImpl extends EgovAbstractServiceImpl implements
return resultData;
}
/**
* ( )
*
* @param existingData
* @param apiResponse API
* @param userId ID
* @return
*/
private String executeComparisonLogic(CarFfnlgTrgtVO existingData, VehicleApiResponseVO apiResponse, String userId) {
// ========== 방법 선택 (둘 중 하나만 사용) ==========
// 방법1: Chain of Responsibility 패턴 사용 (복잡하지만 확장성 좋음)
// return executeWithChainPattern(existingData, apiResponse, userId);
// 방법2: 일반 Service/Impl 패턴 사용 (간단하고 명확함) ⭐ 현재 사용 중
return executeWithServicePattern(existingData, apiResponse, userId);
}
/**
* 1: Chain of Responsibility
*/
@SuppressWarnings("unused")
private String executeWithChainPattern(CarFfnlgTrgtVO existingData, VehicleApiResponseVO apiResponse, String userId) {
ComparisonContext context = ComparisonContext.builder()
.existingData(existingData)
.apiResponse(apiResponse)
.userId(userId)
.build();
ComparisonResult result = comparisonRuleProcessor.process(context);
return result != null && result.isApplied() ? result.getStatusCode() : null;
}
/**
* 2: Service/Impl
*/
private String executeWithServicePattern(CarFfnlgTrgtVO existingData, VehicleApiResponseVO apiResponse, String userId) {
return comparisonService.executeComparison(existingData, apiResponse, userId);
}
}

@ -1,5 +1,6 @@
package go.kr.project.carInspectionPenalty.registration.service.impl;
import egovframework.exception.MessageException;
import go.kr.project.api.model.VehicleApiResponseVO;
import go.kr.project.api.model.response.BasicResponse;
import go.kr.project.carInspectionPenalty.registration.mapper.CarFfnlgTrgtMapper;
@ -41,12 +42,6 @@ public class ComparisonServiceImpl extends EgovAbstractServiceImpl implements Co
String vhclno = existingData.getVhclno();
log.info("========== 비교 로직 시작: {} ==========", vhclno);
// API 응답 데이터 유효성 검사
if (!isValidApiResponse(apiResponse)) {
log.warn("API 응답 데이터가 유효하지 않습니다. 차량번호: {}", vhclno);
return null;
}
BasicResponse.Record basicInfo = apiResponse.getBasicInfo().getRecord().get(0);
// ========== 1. 상품용 체크 ==========
@ -107,7 +102,7 @@ public class ComparisonServiceImpl extends EgovAbstractServiceImpl implements Co
int updateCount = carFfnlgTrgtMapper.update(existingData);
if (updateCount == 0) {
throw new RuntimeException(String.format("[상품용] 업데이트 실패: %s", vhclno));
throw new MessageException(String.format("[상품용] 업데이트 실패: %s", vhclno));
}
log.info("[상품용] 처리 완료! 차량번호: {}", vhclno);
@ -205,7 +200,7 @@ public class ComparisonServiceImpl extends EgovAbstractServiceImpl implements Co
int updateCount = carFfnlgTrgtMapper.update(existingData);
if (updateCount == 0) {
throw new RuntimeException(String.format("[이첩] 업데이트 실패: %s", vhclno));
throw new MessageException(String.format("[이첩] 업데이트 실패: %s", vhclno));
}
log.info("[이첩] 처리 완료! 차량번호: {}, 시군구: {}({}), 사유: {}", vhclno, sggNm, sggCd, reason);
@ -240,24 +235,11 @@ public class ComparisonServiceImpl extends EgovAbstractServiceImpl implements Co
int updateCount = carFfnlgTrgtMapper.update(existingData);
if (updateCount == 0) {
throw new RuntimeException(String.format("[내사종결] 업데이트 실패: %s", vhclno));
throw new MessageException(String.format("[내사종결] 업데이트 실패: %s", vhclno));
}
log.info("[내사종결] 처리 완료! 차량번호: {}", vhclno);
return "04";
}
// ========================================
// 유틸리티 메서드
// ========================================
/**
* API
*/
private boolean isValidApiResponse(VehicleApiResponseVO apiResponse) {
return apiResponse != null
&& apiResponse.getBasicInfo() != null
&& apiResponse.getBasicInfo().getRecord() != null
&& !apiResponse.getBasicInfo().getRecord().isEmpty();
}
}

@ -57,7 +57,7 @@
<ul class="box_title" style="display: flex; justify-content: space-between; align-items: center;">
<li class="tit">과태료 대상 목록</li>
<li class="rig">
<button type="button" id="callApiBtn" class="newbtn bg2-1">API 호출</button>
<button type="button" id="callApiBtn" class="newbtn bg2-1">그리드 선택 API 호출</button>
<span id="totalCount" class="total-count" style="padding-left: 25px;padding-right: 25px;">총 0건</span>
<select id="perPageSelect" class="input" style="width: 112px; ">
@ -463,7 +463,6 @@
alert("삭제가 완료되었습니다.");
CarFfnlgTrgtList.grid.reload();
}).fail(function() {
alert("삭제 중 오류가 발생했습니다.");
});
},
@ -520,7 +519,6 @@
},
error: function(xhr, status, error) {
console.error("API 호출 실패:", error);
alert("API 호출 중 오류가 발생했습니다.\n" + error);
}
});
}

Loading…
Cancel
Save