From 2940206b6cc023b7cf5297198f2187c022f3e1e5 Mon Sep 17 00:00:00 2001 From: AngeloCaporaso Date: Tue, 17 Sep 2024 18:12:41 +0200 Subject: [PATCH] [PAGOPA-2146] feat(Service): Add recover by IUV --- .../model/RecoveryReceiptPaymentResponse.java | 3 +- .../service/RecoveryService.java | 234 +++++++++--------- 2 files changed, 118 insertions(+), 119 deletions(-) diff --git a/src/main/java/it/gov/pagopa/wispconverter/controller/model/RecoveryReceiptPaymentResponse.java b/src/main/java/it/gov/pagopa/wispconverter/controller/model/RecoveryReceiptPaymentResponse.java index a135ffe1..eaaf1a1f 100644 --- a/src/main/java/it/gov/pagopa/wispconverter/controller/model/RecoveryReceiptPaymentResponse.java +++ b/src/main/java/it/gov/pagopa/wispconverter/controller/model/RecoveryReceiptPaymentResponse.java @@ -13,4 +13,5 @@ public class RecoveryReceiptPaymentResponse { private String iuv; private String ccp; -} + private String ci; +} \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/wispconverter/service/RecoveryService.java b/src/main/java/it/gov/pagopa/wispconverter/service/RecoveryService.java index 657e8f17..ad0e7452 100644 --- a/src/main/java/it/gov/pagopa/wispconverter/service/RecoveryService.java +++ b/src/main/java/it/gov/pagopa/wispconverter/service/RecoveryService.java @@ -27,7 +27,9 @@ import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.Comparator; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.CompletableFuture; @Service @@ -43,10 +45,12 @@ public class RecoveryService { private static final String STATUS_RT_SEND_SUCCESS = "RT_SEND_SUCCESS"; - private static final String MOCK_NOTICE_NUMBER = "348000000000000000"; + private static final String CREATED_NEW_PAYMENT_POSITION_IN_GPD = "CREATED_NEW_PAYMENT_POSITION_IN_GPD"; private static final String RECOVERY_VALID_START_DATE = "2024-09-03"; + private static final String MOCK_NOTICE_NUMBER = "348000000000000000"; + private static final List BUSINESS_PROCESSES = List.of("receipt-ok", "receipt-ko", "ecommerce-hang-timeout-trigger"); private final ReceiptController receiptController; @@ -59,88 +63,56 @@ public class RecoveryService { private final ReService reService; - @Value("${wisp-converter.cached-requestid-mapping.ttl.minutes}") - private Long requestIDMappingTTL; + private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; + + @Value("${wisp-converter.cached-requestid-mapping.ttl.minutes:1440}") + public Long requestIDMappingTTL; @Value("${wisp-converter.recovery.receipt-generation.wait-time.minutes:60}") - private Long receiptGenerationWaitTime; + public Long receiptGenerationWaitTime; public void recoverReceiptKO(String creditorInstitution, String iuv, String dateFrom, String dateTo) { - List reEvents = reEventRepository.findByIuvAndOrganizationId(dateFrom, dateTo, iuv, creditorInstitution); + if(!areValidDates(dateFrom, dateTo)) { + throw new AppException(AppErrorCodeMessageEnum.ERROR, String.format("The lower bound cannot be lower than [%s], the upper bound cannot be higher than [%s]", + RECOVERY_VALID_START_DATE, LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")))); + } - List rptEvents = reEvents.stream() - .filter(event -> RPT_ACCETTATA_NODO.equals(event.getStatus())) - .sorted(Comparator.comparing(ReEventEntity::getInsertedTimestamp)) - .toList(); - - List rtSendEvents = reEvents.stream() - .filter(event -> RT_SEND_SUCCESS.equals(event.getStatus())) - .sorted(Comparator.comparing(ReEventEntity::getInsertedTimestamp)) - .toList(); - - List cacheEvents = reEvents.stream() - .filter(event -> EVENT_TYPE_FOR_RECEIPTKO_SEARCH.equals(event.getStatus())) - .sorted(Comparator.comparing(ReEventEntity::getInsertedTimestamp)) - .toList(); - // date validation - if(rptEvents.size() > rtSendEvents.size()) { - String noticeNumber, sessionId; - Long ttlInSeconds = this.requestIDMappingTTL; - // it is required to send a receipt-ko - if(!cacheEvents.isEmpty()) { - ReEventEntity event = cacheEvents.get(cacheEvents.size() - 1); - noticeNumber = event.getNoticeNumber(); - sessionId = event.getSessionId(); - } else { - ReEventEntity event = rptEvents.get(rptEvents.size() - 1); - sessionId = event.getSessionId(); - noticeNumber = MOCK_NOTICE_NUMBER; - ttlInSeconds = 10L; - } + List reEvents = reEventRepository.findByIuvAndOrganizationId(dateFrom, dateTo, iuv, creditorInstitution); + List events = reEvents.stream() + .sorted(Comparator.comparing(ReEventEntity::getInsertedTimestamp)) + .toList(); - String navToIuvMapping = String.format(DecouplerService.MAP_CACHING_KEY_TEMPLATE, creditorInstitution, noticeNumber); - String iuvToSessionIdMapping = String.format(DecouplerService.CACHING_KEY_TEMPLATE, creditorInstitution, iuv); + Set noRedirectSet = Set.of("RPT_ACCETTATA_NODO", "RPT_PARCHEGGIATA_NODO"); + Set breakAfterRedirectSet = Set.of("GENERATED_NAV_FOR_NEW_PAYMENT_POSITION", "CREATED_NEW_PAYMENT_POSITION_IN_GPD", + "GENERATED_CACHE_ABOUT_RPT_FOR_DECOUPLER", "GENERATED_CACHE_ABOUT_RPT_FOR_RT_GENERATION", "SAVED_RPT_IN_CART_RECEIVED_REDIRECT_URL_FROM_CHECKOUT"); - this.cacheRepository.insert(navToIuvMapping, iuvToSessionIdMapping, ttlInSeconds, ChronoUnit.SECONDS, true); - this.cacheRepository.insert(iuvToSessionIdMapping, sessionId, ttlInSeconds, ChronoUnit.SECONDS, true); + if(!events.isEmpty()) { + ReEventEntity lastEvent = events.get(0); + String status = lastEvent.getStatus(); + String noticeNumber; - try { - MDC.put(Constants.MDC_BUSINESS_PROCESS, "receipt-ko"); - generateRE(Constants.PAA_INVIA_RT, null, InternalStepStatus.RT_START_RECONCILIATION_PROCESS, creditorInstitution, iuv, noticeNumber, "NA", sessionId); - String receiptKoRequest = ReceiptDto.builder() - .fiscalCode(creditorInstitution) - .noticeNumber(noticeNumber) - .build() - .toString(); + if(noRedirectSet.contains(status)) { + noticeNumber = MOCK_NOTICE_NUMBER; + } else if(breakAfterRedirectSet.contains(status)) { + noticeNumber = lastEvent.getNoticeNumber(); + } else return; - this.receiptController.receiptKo(receiptKoRequest); - generateRE(Constants.PAA_INVIA_RT, "Success", InternalStepStatus.RT_END_RECONCILIATION_PROCESS, creditorInstitution, iuv, noticeNumber, "NA", sessionId); - MDC.remove(Constants.MDC_BUSINESS_PROCESS); - } catch (Exception e) { - generateRE(Constants.PAA_INVIA_RT, "Failure", InternalStepStatus.RT_END_RECONCILIATION_PROCESS, creditorInstitution, iuv, noticeNumber, "NA", sessionId); - throw new AppException(e, AppErrorCodeMessageEnum.ERROR, e.getMessage()); - } + this.recoverReceiptKO(creditorInstitution, noticeNumber, iuv, lastEvent.getSessionId(), lastEvent.getCcp(), dateFrom, dateTo); } - } public RecoveryReceiptResponse recoverReceiptKOForCreditorInstitution(String creditorInstitution, String dateFrom, String dateTo) { - - LocalDate lowerLimit = LocalDate.parse(RECOVERY_VALID_START_DATE, DateTimeFormatter.ISO_LOCAL_DATE); - if (LocalDate.parse(dateFrom, DateTimeFormatter.ISO_LOCAL_DATE).isBefore(lowerLimit)) { - throw new AppException(AppErrorCodeMessageEnum.ERROR, String.format("The lower bound cannot be lower than [%s]", RECOVERY_VALID_START_DATE)); - } - LocalDate now = LocalDate.now(); - LocalDate parse = LocalDate.parse(dateTo, DateTimeFormatter.ISO_LOCAL_DATE); - if (parse.isAfter(now)) { - throw new AppException(AppErrorCodeMessageEnum.ERROR, String.format("The upper bound cannot be higher than [%s]", now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")))); + if(!areValidDates(dateFrom, dateTo)) { + throw new AppException(AppErrorCodeMessageEnum.ERROR, String.format("The lower bound cannot be lower than [%s], the upper bound cannot be higher than [%s]", + RECOVERY_VALID_START_DATE, now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")))); } + LocalDate parse = LocalDate.parse(dateTo, DateTimeFormatter.ISO_LOCAL_DATE); String dateToRefactored; if (now.isEqual(parse)) { ZonedDateTime nowMinusMinutes = ZonedDateTime.now(ZoneOffset.UTC).minusMinutes(receiptGenerationWaitTime); - dateToRefactored = nowMinusMinutes.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + dateToRefactored = nowMinusMinutes.format(DateTimeFormatter.ofPattern(DATE_FORMAT)); log.info("Upper bound forced to {}", dateToRefactored); } else { dateToRefactored = dateTo + " 23:59:59"; @@ -149,12 +121,13 @@ public RecoveryReceiptResponse recoverReceiptKOForCreditorInstitution(String cre List receiptRTs = rtRepository.findByOrganizationId(creditorInstitution, dateFrom, dateToRefactored); List paymentsToReconcile = receiptRTs.stream().map(entity -> RecoveryReceiptPaymentResponse.builder() - .iuv(entity.getIuv()) - .ccp(entity.getCcp()) - .build()) - .toList(); + .iuv(entity.getIuv()) + .ccp(entity.getCcp()) + .ci(entity.getIdDominio()) + .build()) + .toList(); - CompletableFuture executeRecovery = recoverReceiptKOAsync(dateFrom, dateTo, creditorInstitution, paymentsToReconcile); + CompletableFuture executeRecovery = recoverReceiptKOAsync(dateFrom, dateTo, paymentsToReconcile); executeRecovery .thenAccept(value -> log.info("Reconciliation for creditor institution [{}] in date range [{}-{}] completed!", creditorInstitution, dateFrom, dateTo)) .exceptionally(e -> { @@ -163,65 +136,90 @@ public RecoveryReceiptResponse recoverReceiptKOForCreditorInstitution(String cre }); return RecoveryReceiptResponse.builder() - .payments(paymentsToReconcile) - .build(); + .payments(paymentsToReconcile) + .build(); } - private CompletableFuture recoverReceiptKOAsync(String dateFrom, String dateTo, String creditorInstitution, List paymentsToReconcile) { - - return CompletableFuture.supplyAsync(() -> { - - for (RecoveryReceiptPaymentResponse payment : paymentsToReconcile) { - - String iuv = payment.getIuv(); - String ccp = payment.getCcp(); - - try { - List reEvents = reEventRepository.findByIuvAndOrganizationId(dateFrom, dateTo, iuv, creditorInstitution); - - List filteredEvents = reEvents.stream() - .filter(event -> EVENT_TYPE_FOR_RECEIPTKO_SEARCH.equals(event.getStatus())) - .filter(event -> ccp.equals(event.getCcp())) - .sorted(Comparator.comparing(ReEventEntity::getInsertedTimestamp)) - .toList(); + private boolean areValidDates(String dateFrom, String dateTo) { + LocalDate lowerLimit = LocalDate.parse(RECOVERY_VALID_START_DATE, DateTimeFormatter.ISO_LOCAL_DATE); + if (LocalDate.parse(dateFrom, DateTimeFormatter.ISO_LOCAL_DATE).isBefore(lowerLimit)) { + throw new AppException(AppErrorCodeMessageEnum.ERROR, String.format("The lower bound cannot be lower than [%s]", RECOVERY_VALID_START_DATE)); + } - int numberOfEvents = filteredEvents.size(); - if (numberOfEvents > 0) { + LocalDate now = LocalDate.now(); + LocalDate parse = LocalDate.parse(dateTo, DateTimeFormatter.ISO_LOCAL_DATE); + if (parse.isAfter(now)) { + throw new AppException(AppErrorCodeMessageEnum.ERROR, String.format("The upper bound cannot be higher than [%s]", now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")))); + } - ReEventEntity event = filteredEvents.get(numberOfEvents - 1); - String noticeNumber = event.getNoticeNumber(); - String sessionId = event.getSessionId(); + return true; + } - // search by sessionId, then filter by status=RT_SEND_SUCCESS. If there is zero, then proceed - List reEventsRT = reEventRepository.findBySessionIdAndStatus(dateFrom, dateTo, sessionId, STATUS_RT_SEND_SUCCESS); + private CompletableFuture recoverReceiptKOAsync(String dateFrom, String dateTo, List paymentsToReconcile) { + return CompletableFuture.supplyAsync(() -> recoverReceiptKOByRecoveryPayment(dateFrom, dateTo, paymentsToReconcile)); + } - if (reEventsRT.isEmpty()) { - String navToIuvMapping = String.format(DecouplerService.MAP_CACHING_KEY_TEMPLATE, creditorInstitution, noticeNumber); - String iuvToSessionIdMapping = String.format(DecouplerService.CACHING_KEY_TEMPLATE, creditorInstitution, iuv); - this.cacheRepository.insert(navToIuvMapping, iuvToSessionIdMapping, this.requestIDMappingTTL); - this.cacheRepository.insert(iuvToSessionIdMapping, sessionId, this.requestIDMappingTTL); + private boolean recoverReceiptKOByRecoveryPayment(String dateFrom, String dateTo, List paymentsToReconcile) { - MDC.put(Constants.MDC_BUSINESS_PROCESS, "receipt-ko"); - generateRE(Constants.PAA_INVIA_RT, null, InternalStepStatus.RT_START_RECONCILIATION_PROCESS, creditorInstitution, iuv, noticeNumber, ccp, sessionId); - String receiptKoRequest = ReceiptDto.builder() - .fiscalCode(creditorInstitution) - .noticeNumber(noticeNumber) - .build() - .toString(); - this.receiptController.receiptKo(receiptKoRequest); - generateRE(Constants.PAA_INVIA_RT, "Success", InternalStepStatus.RT_END_RECONCILIATION_PROCESS, creditorInstitution, iuv, noticeNumber, ccp, sessionId); - MDC.remove(Constants.MDC_BUSINESS_PROCESS); - } - } + for (RecoveryReceiptPaymentResponse payment : paymentsToReconcile) { + String iuv = payment.getIuv(); + String ccp = payment.getCcp(); + String ci = payment.getCi(); - } catch (Exception e) { - generateRE(Constants.PAA_INVIA_RT, "Failure", InternalStepStatus.RT_END_RECONCILIATION_PROCESS, creditorInstitution, iuv, null, ccp, null); - throw new AppException(e, AppErrorCodeMessageEnum.ERROR, e.getMessage()); + try { + List reEvents = reEventRepository.findByIuvAndOrganizationId(dateFrom, dateTo, iuv, ci); + + List filteredEvents = reEvents.stream() + .filter(event -> EVENT_TYPE_FOR_RECEIPTKO_SEARCH.equals(event.getStatus())) + .filter(event -> ccp.equals(event.getCcp())) + .sorted(Comparator.comparing(ReEventEntity::getInsertedTimestamp)) + .toList(); + + int numberOfEvents = filteredEvents.size(); + if (numberOfEvents > 0) { + ReEventEntity event = filteredEvents.get(numberOfEvents - 1); + String noticeNumber = event.getNoticeNumber(); + String sessionId = event.getSessionId(); + + log.info("[RECOVERY-MISSING-RT] Recovery with receipt-ko for ci = {}, iuv = {}, ccp = {}, sessionId = {}", ci, iuv, ccp, sessionId); + this.recoverReceiptKO(ci, noticeNumber, iuv, sessionId, ccp, dateFrom, dateTo); } + } catch (Exception e) { + generateRE(Constants.PAA_INVIA_RT, "Failure", InternalStepStatus.RT_END_RECONCILIATION_PROCESS, ci, iuv, null, ccp, null); + throw new AppException(e, AppErrorCodeMessageEnum.ERROR, e.getMessage()); } + } + + return true; + } - return true; - }); + // check if there is a successful RT submission, if there isn't prepare cached data and send receipt-ko + public void recoverReceiptKO(String ci, String noticeNumber, String iuv, String sessionId, String ccp, String dateFrom, String dateTo) { + // search by sessionId, then filter by status=RT_SEND_SUCCESS. If there is zero, then proceed + List reEventsRT = reEventRepository.findBySessionIdAndStatus(dateFrom, dateTo, sessionId, STATUS_RT_SEND_SUCCESS); + + if (reEventsRT.isEmpty()) { + String navToIuvMapping = String.format(DecouplerService.MAP_CACHING_KEY_TEMPLATE, ci, noticeNumber); + String iuvToSessionIdMapping = String.format(DecouplerService.CACHING_KEY_TEMPLATE, ci, iuv); + this.cacheRepository.insert(navToIuvMapping, iuvToSessionIdMapping, this.requestIDMappingTTL, ChronoUnit.MINUTES,true); + this.cacheRepository.insert(iuvToSessionIdMapping, sessionId, this.requestIDMappingTTL, ChronoUnit.MINUTES,true); + + MDC.put(Constants.MDC_BUSINESS_PROCESS, "receipt-ko"); + generateRE(Constants.PAA_INVIA_RT, null, InternalStepStatus.RT_START_RECONCILIATION_PROCESS, ci, iuv, noticeNumber, ccp, sessionId); + String receiptKoRequest = ReceiptDto.builder() + .fiscalCode(ci) + .noticeNumber(noticeNumber) + .build() + .toString(); + try { + this.receiptController.receiptKo(receiptKoRequest); + } catch (Exception e) { + generateRE(Constants.PAA_INVIA_RT, "Failure", InternalStepStatus.RT_END_RECONCILIATION_PROCESS, ci, iuv, noticeNumber, ccp, sessionId); + throw new AppException(e, AppErrorCodeMessageEnum.ERROR, e.getMessage()); + } + generateRE(Constants.PAA_INVIA_RT, "Success", InternalStepStatus.RT_END_RECONCILIATION_PROCESS, ci, iuv, noticeNumber, ccp, sessionId); + MDC.remove(Constants.MDC_BUSINESS_PROCESS); + } } private void generateRE(String primitive, String operationStatus, InternalStepStatus status, String domainId, String iuv, String noticeNumber, String ccp, String sessionId) { @@ -239,4 +237,4 @@ private void generateRE(String primitive, String operationStatus, InternalStepSt .build(); reService.addRe(reEvent); } -} +} \ No newline at end of file