Skip to content

Commit

Permalink
feat: add API for retrieve pending receipts
Browse files Browse the repository at this point in the history
  • Loading branch information
andrea-deri committed Nov 26, 2024
1 parent a4a3685 commit 1c8fa85
Show file tree
Hide file tree
Showing 11 changed files with 243 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import it.gov.pagopa.wispconverter.technicalsupport.controller.model.experimental.monitoring.ReceiptStatusSnapshotResponse;
import it.gov.pagopa.wispconverter.technicalsupport.controller.model.experimental.monitoring.ReceiptsStatusSnapshot;
import it.gov.pagopa.wispconverter.technicalsupport.controller.model.experimental.monitoring.*;
import it.gov.pagopa.wispconverter.technicalsupport.controller.model.experimental.payment.*;
import it.gov.pagopa.wispconverter.technicalsupport.service.ExperimentalService;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -144,19 +143,31 @@ public PaymentFlowStatusResponse findStatusByIuv(

@Operation(summary = "Get snapshot of receipt status", description = "Retrieve a snapshot of receipt status at specific slot time.", tags = {"Technical Support - Enhanced features"})
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successfully extracted snapshot", content = @Content(schema = @Schema(implementation = ReceiptStatusSnapshotResponse.class)))
@ApiResponse(responseCode = "200", description = "Successfully extracted snapshot", content = @Content(schema = @Schema(implementation = ReceiptsStatusSnapshotResponse.class)))
})
@GetMapping(value = "/monitoring/receipts/snapshot", produces = MediaType.APPLICATION_JSON_VALUE)
public ReceiptStatusSnapshotResponse extractReceiptSnapshot(
public ReceiptsStatusSnapshotResponse extractReceiptSnapshot(
@RequestParam(name = DATE_FROM) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) @Schema(example = "2024-01-01T12:00:00", description = "Lower limit date time (in yyyy-MM-ddThh:mm:ss) in 'Europe/Rome' timezone") LocalDateTime dateFrom,
@RequestParam(name = DATE_TO) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) @Schema(example = "2024-01-01T13:00:00", description = "Upper limit date time (in yyyy-MM-ddThh:mm:ss) in 'Europe/Rome' timezone") LocalDateTime dateTo) {

List<ReceiptsStatusSnapshot> receiptsStatusSnapshots = experimentalService.extractReceiptSnapshot(dateFrom, dateTo);
return ReceiptStatusSnapshotResponse.builder()
return ReceiptsStatusSnapshotResponse.builder()
.snapshot(receiptsStatusSnapshots)
.lowerBoundDate(dateFrom)
.upperBoundDate(dateTo)
.build();
}

@Operation(summary = "Get pending receipts", description = "Retrieve the list of pending receipt that could have not been sent to creditor institution", tags = {"Technical Support - Enhanced features"})
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successfully extracted data", content = @Content(schema = @Schema(implementation = ReceiptsStatusSnapshotResponse.class)))
})
@PostMapping(value = "/monitoring/receipts/pending", produces = MediaType.APPLICATION_JSON_VALUE)
public PendingReceiptsResponse extractPendingReceipts(@RequestBody PendingReceiptsRequest request) {

List<PendingReceipt> pendingReceipts = experimentalService.extractPendingReceipts(request);
return PendingReceiptsResponse.builder()
.pendingReceipts(pendingReceipts)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package it.gov.pagopa.wispconverter.technicalsupport.controller.model.experimental.monitoring;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import it.gov.pagopa.wispconverter.technicalsupport.controller.model.experimental.payment.PaymentIdentifier;
import lombok.*;

@Builder
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class PendingReceipt {

@JsonProperty("payment")
private PaymentIdentifier payment;

@JsonProperty("receipt_to_send")
private String receiptToSend;

@JsonProperty("receipt_content")
private String receiptContent;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package it.gov.pagopa.wispconverter.technicalsupport.controller.model.experimental.monitoring;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import org.springframework.format.annotation.DateTimeFormat;

import java.time.LocalDateTime;
import java.util.Set;

@Builder
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class PendingReceiptsRequest {

@Schema(example = "2024-01-01T12:00:00", description = "Lower limit date time (in yyyy-MM-ddThh:mm:ss) in 'Europe/Rome' timezone")
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
@JsonProperty("lower_bound_date")
private LocalDateTime lowerBoundDate;

@Schema(example = "2024-01-01T12:00:00", description = "Upper limit date time (in yyyy-MM-ddThh:mm:ss) in 'Europe/Rome' timezone")
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
@JsonProperty("upper_bound_date")
private LocalDateTime upperBoundDate;

@JsonProperty("creditor_institution")
private String creditorInstitution;

@JsonProperty("iuvs")
private Set<String> iuvs;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package it.gov.pagopa.wispconverter.technicalsupport.controller.model.experimental.monitoring;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;

import java.util.List;

@Builder
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class PendingReceiptsResponse {

@JsonProperty("pending_receipts")
private List<PendingReceipt> pendingReceipts;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ReceiptStatusSnapshotResponse {
public class ReceiptsStatusSnapshotResponse {

@JsonProperty("lower_bound_date")
private LocalDateTime lowerBoundDate;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,8 @@
@JsonInclude(JsonInclude.Include.NON_NULL)
public class PaymentFlowStatus {

@JsonProperty("iuv")
private String iuv;

@JsonProperty("domain_id")
private String domainId;

@JsonProperty("ccp")
private String ccp;
@JsonProperty("payment_identifier")
private PaymentIdentifier paymentIdentifier;

@JsonProperty("payment_details")
private PaymentFlowDetail paymentDetails;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package it.gov.pagopa.wispconverter.technicalsupport.controller.model.experimental.payment;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;

@Builder
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class PaymentIdentifier {

@JsonProperty("iuv")
private String iuv;

@JsonProperty("domain_id")
private String domainId;

@JsonProperty("ccp")
private String ccp;

@JsonProperty("session_id")
private String sessionId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Set;

@Repository
Expand All @@ -20,4 +21,24 @@ public interface RTRepository extends CosmosRepository<RTEntity, String> {
"GROUP BY c.receiptStatus")
Set<RTGroupedByStatusEntity> findByTimestampGroupByStatus(@Param("dateFrom") String dateFrom,
@Param("dateTo") String dateTo);

@Query("SELECT * " +
"FROM c " +
"WHERE c._ts*1000 >= DateTimeToTimestamp(@dateFrom) AND c._ts*1000 < DateTimeToTimestamp(@dateTo) " +
"AND c.domainId = @domainId " +
"AND c.rt = null")
List<RTEntity> findAllInPendingStatus(@Param("dateFrom") String dateFrom,
@Param("dateTo") String dateTo,
@Param("domainId") String domainId);

@Query("SELECT * " +
"FROM c " +
"WHERE c._ts*1000 >= DateTimeToTimestamp(@dateFrom) AND c._ts*1000 < DateTimeToTimestamp(@dateTo) " +
"AND c.domainId = @domainId " +
"AND c.iuv IN (@iuvs) " +
"AND c.rt = null")
List<RTEntity> findInPendingStatus(@Param("dateFrom") String dateFrom,
@Param("dateTo") String dateTo,
@Param("domainId") String domainId,
@Param("iuvs") Set<String> iuvs);
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,42 @@ public class ReEventDataExplorerRepository {
| where noticeNumber == '%s' and idDominio == '%s'
| order by insertedTimestamp""";

public static final String QUERY_FIND_SENDRTV2_BY_IUV = """
ReEvent
| where insertedTimestamp >= todatetime("%sT00:00:00")
| where insertedTimestamp <= todatetime("%sT23:59:59")
| where creditorReferenceId == '%s' and idDominio == '%s'
| where tipoEvento contains 'sendRTV2'
| where sottoTipoEvento == 'REQ'""";

private final ReEventDataExplorerMapper reEventDataExplorerMapper;

private final Client kustoClient;

@Value("${azure.dataexplorer.dbName}")
private String database;

public List<ReEventDataExplorerEntity> find(String dateFrom, String dateTo, String noticeNumber, String domainId, String ccp) {

List<ReEventDataExplorerEntity> result = new LinkedList<>();
public List<ReEventDataExplorerEntity> find(String dateFrom, String dateTo, String noticeNumber, String domainId, String ccp) {

String query;
if (ccp == null || ccp.isEmpty()) {
query = String.format(QUERY_FIND_BY_NOTICE_NUMBER, dateFrom, dateTo, noticeNumber, domainId);
} else {
query = String.format(QUERY_FIND_BY_NOTICE_NUMBER_AND_CCP, dateFrom, dateTo, noticeNumber, domainId, ccp);
}
return executeQuery(query);
}

public List<ReEventDataExplorerEntity> findSendRTV2Event(String dateFrom, String dateTo, String iuv, String domainId) {

String query = String.format(QUERY_FIND_SENDRTV2_BY_IUV, dateFrom, dateTo, iuv, domainId);
return executeQuery(query);
}

public List<ReEventDataExplorerEntity> executeQuery(String query) {

List<ReEventDataExplorerEntity> result = new LinkedList<>();
try {
KustoOperationResult response = kustoClient.execute(database, query);
KustoResultSetTable primaryResults = response.getPrimaryResults();
Expand All @@ -62,7 +80,6 @@ public List<ReEventDataExplorerEntity> find(String dateFrom, String dateTo, Stri
} catch (Exception e) {
throw new AppException(AppError.INTERNAL_SERVER_ERROR, e);
}

return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
import it.gov.pagopa.wispconverter.technicalsupport.controller.mapper.ReEventMapper;
import it.gov.pagopa.wispconverter.technicalsupport.controller.model.EventCategoryEnum;
import it.gov.pagopa.wispconverter.technicalsupport.controller.model.ReEvent;
import it.gov.pagopa.wispconverter.technicalsupport.controller.model.experimental.monitoring.PendingReceipt;
import it.gov.pagopa.wispconverter.technicalsupport.controller.model.experimental.monitoring.PendingReceiptsRequest;
import it.gov.pagopa.wispconverter.technicalsupport.controller.model.experimental.monitoring.ReceiptsStatusSnapshot;
import it.gov.pagopa.wispconverter.technicalsupport.controller.model.experimental.payment.PaymentFlow;
import it.gov.pagopa.wispconverter.technicalsupport.controller.model.experimental.payment.PaymentFlowDetail;
import it.gov.pagopa.wispconverter.technicalsupport.controller.model.experimental.payment.PaymentFlowStatus;
import it.gov.pagopa.wispconverter.technicalsupport.controller.model.experimental.payment.PaymentFlowStep;
import it.gov.pagopa.wispconverter.technicalsupport.controller.model.experimental.payment.*;
import it.gov.pagopa.wispconverter.technicalsupport.repository.RTRepository;
import it.gov.pagopa.wispconverter.technicalsupport.repository.ReEventDataExplorerRepository;
import it.gov.pagopa.wispconverter.technicalsupport.repository.ReEventExperimentalRepository;
Expand Down Expand Up @@ -205,9 +204,11 @@ public List<PaymentFlowStatus> getPaymentStatusFindByIuv(LocalDate dateFromAsLoc
Optional<RTEntity> receipt = rtRepository.findById(id, new PartitionKey(id));

paymentFlows.add(PaymentFlowStatus.builder()
.domainId(paymentDomainId)
.iuv(paymentIuv)
.ccp(paymentCcp)
.paymentIdentifier(PaymentIdentifier.builder()
.domainId(paymentDomainId)
.iuv(paymentIuv)
.ccp(paymentCcp)
.build())
.paymentOutcome(receipt.isPresent() ? receipt.get().getReceiptType() : "NOT_FOUND")
.receiptSendingStatus(receipt.isPresent() ? receipt.get().getReceiptStatus() : "NOT_FOUND")
.paymentDetails(extractPaymentFlowDetail(events, paymentDomainId, paymentIuv))
Expand Down Expand Up @@ -238,6 +239,65 @@ public List<ReceiptsStatusSnapshot> extractReceiptSnapshot(LocalDateTime dateFro
.toList();
}


public List<PendingReceipt> extractPendingReceipts(PendingReceiptsRequest request) {

List<PendingReceipt> pendingReceipts = new LinkedList<>();
String dateTimeFrom = CommonUtility.timestampFromInstant(request.getLowerBoundDate().minusHours(1));
String dateTimeTo = CommonUtility.timestampFromInstant(request.getUpperBoundDate().minusHours(1));

String dateFrom = CommonUtility.partitionKeyFromInstant(request.getLowerBoundDate().toLocalDate());
String dateTo = CommonUtility.partitionKeyFromInstant(request.getUpperBoundDate().toLocalDate());

List<RTEntity> rtsInPendingStatus;

Set<String> iuvs = request.getIuvs();
if (iuvs == null || iuvs.isEmpty()) {
rtsInPendingStatus = rtRepository.findAllInPendingStatus(dateTimeFrom, dateTimeTo, request.getCreditorInstitution());
} else {
rtsInPendingStatus = rtRepository.findInPendingStatus(dateTimeFrom, dateTimeTo, request.getCreditorInstitution(), iuvs);
}

for (RTEntity rtInPendingStatus : rtsInPendingStatus) {

String iuv = rtInPendingStatus.getIuv();
String domainId = rtInPendingStatus.getDomainId();

List<ReEventDataExplorerEntity> events = reEventDataExplorerRepository.findSendRTV2Event(dateFrom, dateTo, iuv, domainId);

if (events == null || events.isEmpty()) {

pendingReceipts.add(PendingReceipt.builder()
.receiptToSend("KO")
.payment(PaymentIdentifier.builder()
.iuv(iuv)
.domainId(domainId)
.sessionId(rtInPendingStatus.getSessionId())
.build())
.build());

} else {

for (ReEventDataExplorerEntity event : events) {

pendingReceipts.add(PendingReceipt.builder()
.receiptContent(String.format("{\"content\":\"%s\"}", CommonUtility.decodeBase64(event.getPayload())))
.receiptToSend("OK")
.payment(PaymentIdentifier.builder()
.iuv(iuv)
.domainId(domainId)
.sessionId(rtInPendingStatus.getSessionId())
.build())
.build());
}
}


}

return pendingReceipts;
}

private PaymentFlowDetail extractPaymentFlowDetail(List<ReEvent> events, String domainId, String iuv) {

PaymentFlowDetail paymentFlowDetail = new PaymentFlowDetail();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,18 @@ public static String timestampFromInstant(LocalDateTime insertedTimestamp) {
.format(insertedTimestamp);
}

public static String decodeBase64(String base64String) {
String decodedValue;
if (base64String == null) {
decodedValue = null;
} else if (base64String.isBlank()) {
decodedValue = "";
} else {
decodedValue = new String(Base64.getDecoder().decode(base64String));
}
return decodedValue;
}

public static String decompressGZip(String gzipContent) {
String result;
if (gzipContent == null || gzipContent.isEmpty()) {
Expand Down

0 comments on commit 1c8fa85

Please sign in to comment.