diff --git a/helm/Chart.yaml b/helm/Chart.yaml index d74c263..3848f8b 100644 --- a/helm/Chart.yaml +++ b/helm/Chart.yaml @@ -2,9 +2,13 @@ apiVersion: v2 name: pagopa-wisp-converter-technical-support description: A service that permits to handle converted WISP requests for technical support type: application -version: 0.41.0 -appVersion: 0.3.2 +version: 0.48.0 +appVersion: 0.3.2-7-feat-migrate-report-generator dependencies: - name: microservice-chart version: 3.0.0 repository: "https://pagopa.github.io/aks-microservice-chart-blueprint" + - name: microservice-chart + version: 3.0.0 + repository: "https://pagopa.github.io/aks-microservice-chart-blueprint" + alias: cron diff --git a/helm/values-dev.yaml b/helm/values-dev.yaml index bf0e948..9e7b0ee 100644 --- a/helm/values-dev.yaml +++ b/helm/values-dev.yaml @@ -1,10 +1,10 @@ -microservice-chart: +microservice-chart: µservice-chart namespace: "nodo" nameOverride: "" fullnameOverride: "" image: repository: ghcr.io/pagopa/pagopa-wisp-converter-technical-support - tag: "0.3.2" + tag: "0.3.2-7-feat-migrate-report-generator" pullPolicy: Always livenessProbe: httpGet: @@ -27,7 +27,7 @@ microservice-chart: type: ClusterIP ports: - 8080 - ingress: + ingress: &ingress create: true host: "weudev.nodo.internal.dev.platform.pagopa.it" path: /pagopa-wisp-converter-technical-support(/|$)(.*) @@ -42,14 +42,14 @@ microservice-chart: type: RuntimeDefault securityContext: allowPrivilegeEscalation: false - resources: + resources: &resources requests: memory: "512Mi" cpu: "0.25" limits: memory: "768Mi" cpu: "0.50" - autoscaling: + autoscaling: &autoscaling enable: true minReplica: 1 maxReplica: 1 @@ -61,7 +61,7 @@ microservice-chart: # Required type: Utilization # Allowed types are 'Utilization' or 'AverageValue' value: "75" - envConfig: + envConfig: &envConfig WEBSITE_SITE_NAME: 'pagopawispconverterts' # required to show cloud role name in application insights ENV: 'aks-dev' APP_LOGGING_LEVEL: 'DEBUG' @@ -73,7 +73,7 @@ microservice-chart: DATAEXPLORER_URL: "https://pagopaddataexplorer.westeurope.kusto.windows.net" secretProvider: create: true - envSecrets: + envSecrets: &envSecret # required APPLICATIONINSIGHTS_CONNECTION_STRING: 'azure-insight-connection-string' COSMOS_KEY: 'cosmosdb-wisp-converter-account-key' @@ -112,3 +112,41 @@ microservice-chart: pullPolicy: Always envConfig: {} envSecret: {} +# 1 - Standard service application +app: + !!merge <<: *microservice-chart + ingress: + !!merge <<: *ingress + resources: + !!merge <<: *resources + envConfig: + !!merge <<: *envConfig + envSecret: + !!merge <<: *envSecret +# 2 - Only-cronjob application +cron: + !!merge <<: *microservice-chart + ingress: + !!merge <<: *ingress + path: /pagopa-wisp-converter-technical-support-jobs(/|$)(.*) + resources: + !!merge <<: *resources + requests: + memory: "512Mi" + cpu: "0.25" + limits: + memory: "768Mi" + cpu: "0.50" + autoscaling: + !!merge <<: *autoscaling + enable: false + minReplica: 1 + maxReplica: 1 + envConfig: + !!merge <<: *envConfig + CRONJOB_REPORTGENERATION_ENABLED: 'false' + CRONJOB_REPORTGENERATION_DAILY_SCHEDULE: '00 00 01 * * *' + CRONJOB_REPORTGENERATION_WEEKLY_SCHEDULE: '00 00 06 * * 1' + CRONJOB_REPORTGENERATION_MONTHLY_SCHEDULE: '00 30 06 1 * *' + envSecret: + !!merge <<: *envSecret diff --git a/helm/values-prod.yaml b/helm/values-prod.yaml index 8321d4c..6714037 100644 --- a/helm/values-prod.yaml +++ b/helm/values-prod.yaml @@ -1,10 +1,10 @@ -microservice-chart: +microservice-chart: µservice-chart namespace: "nodo" nameOverride: "" fullnameOverride: "" image: repository: ghcr.io/pagopa/pagopa-wisp-converter-technical-support - tag: "0.3.2" + tag: "0.3.2-7-feat-migrate-report-generator" pullPolicy: Always livenessProbe: httpGet: @@ -27,7 +27,7 @@ microservice-chart: type: ClusterIP ports: - 8080 - ingress: + ingress: &ingress create: true host: "weuprod.nodo.internal.platform.pagopa.it" path: /pagopa-wisp-converter-technical-support(/|$)(.*) @@ -42,14 +42,14 @@ microservice-chart: type: RuntimeDefault securityContext: allowPrivilegeEscalation: false - resources: + resources: &resources requests: memory: "512Mi" cpu: "0.25" limits: memory: "768Mi" cpu: "0.50" - autoscaling: + autoscaling: &autoscaling enable: true minReplica: 1 maxReplica: 2 @@ -61,7 +61,7 @@ microservice-chart: # Required type: Utilization # Allowed types are 'Utilization' or 'AverageValue' value: "75" - envConfig: + envConfig: &envConfig WEBSITE_SITE_NAME: 'pagopawispconverterts' # required to show cloud role name in application insights ENV: 'aks-prod' APP_LOGGING_LEVEL: 'INFO' @@ -73,7 +73,7 @@ microservice-chart: DATAEXPLORER_URL: "https://pagopapdataexplorer.westeurope.kusto.windows.net" secretProvider: create: true - envSecrets: + envSecrets: &envSecret # required APPLICATIONINSIGHTS_CONNECTION_STRING: 'azure-insight-connection-string' COSMOS_KEY: 'cosmosdb-wisp-converter-account-key' @@ -112,3 +112,41 @@ microservice-chart: pullPolicy: Always envConfig: {} envSecret: {} +# 1 - Standard service application +app: + !!merge <<: *microservice-chart + ingress: + !!merge <<: *ingress + resources: + !!merge <<: *resources + envConfig: + !!merge <<: *envConfig + envSecret: + !!merge <<: *envSecret +# 2 - Only-cronjob application +cron: + !!merge <<: *microservice-chart + ingress: + !!merge <<: *ingress + path: /pagopa-wisp-converter-technical-support-jobs(/|$)(.*) + resources: + !!merge <<: *resources + requests: + memory: "512Mi" + cpu: "0.25" + limits: + memory: "1024Mi" + cpu: "0.50" + autoscaling: + !!merge <<: *autoscaling + enable: false + minReplica: 1 + maxReplica: 1 + envConfig: + !!merge <<: *envConfig + CRONJOB_REPORTGENERATION_ENABLED: 'true' + CRONJOB_REPORTGENERATION_DAILY_SCHEDULE: '00 00 00 * * *' + CRONJOB_REPORTGENERATION_WEEKLY_SCHEDULE: '00 00 07 * * 1' + CRONJOB_REPORTGENERATION_MONTHLY_SCHEDULE: '00 30 07 1 * *' + envSecret: + !!merge <<: *envSecret diff --git a/helm/values-uat.yaml b/helm/values-uat.yaml index fa74252..6c14029 100644 --- a/helm/values-uat.yaml +++ b/helm/values-uat.yaml @@ -1,10 +1,10 @@ -microservice-chart: +microservice-chart: µservice-chart namespace: "nodo" nameOverride: "" fullnameOverride: "" image: repository: ghcr.io/pagopa/pagopa-wisp-converter-technical-support - tag: "0.3.2" + tag: "0.3.2-7-feat-migrate-report-generator" pullPolicy: Always livenessProbe: httpGet: @@ -27,7 +27,7 @@ microservice-chart: type: ClusterIP ports: - 8080 - ingress: + ingress: &ingress create: true host: "weuuat.nodo.internal.uat.platform.pagopa.it" path: /pagopa-wisp-converter-technical-support(/|$)(.*) @@ -42,14 +42,14 @@ microservice-chart: type: RuntimeDefault securityContext: allowPrivilegeEscalation: false - resources: + resources: &resources requests: memory: "512Mi" cpu: "0.25" limits: memory: "768Mi" cpu: "0.50" - autoscaling: + autoscaling: &autoscaling enable: true minReplica: 1 maxReplica: 2 @@ -61,7 +61,7 @@ microservice-chart: # Required type: Utilization # Allowed types are 'Utilization' or 'AverageValue' value: "75" - envConfig: + envConfig: &envConfig WEBSITE_SITE_NAME: 'pagopawispconverterts' # required to show cloud role name in application insights ENV: 'aks-uat' APP_LOGGING_LEVEL: 'INFO' @@ -73,7 +73,7 @@ microservice-chart: DATAEXPLORER_URL: "https://pagopaudataexplorer.westeurope.kusto.windows.net" secretProvider: create: true - envSecrets: + envSecrets: &envSecret # required APPLICATIONINSIGHTS_CONNECTION_STRING: 'azure-insight-connection-string' COSMOS_KEY: 'cosmosdb-wisp-converter-account-key' @@ -112,3 +112,41 @@ microservice-chart: pullPolicy: Always envConfig: {} envSecret: {} +# 1 - Standard service application +app: + !!merge <<: *microservice-chart + ingress: + !!merge <<: *ingress + resources: + !!merge <<: *resources + envConfig: + !!merge <<: *envConfig + envSecret: + !!merge <<: *envSecret +# 2 - Only-cronjob application +cron: + !!merge <<: *microservice-chart + ingress: + !!merge <<: *ingress + path: /pagopa-wisp-converter-technical-support-jobs(/|$)(.*) + resources: + !!merge <<: *resources + requests: + memory: "512Mi" + cpu: "0.25" + limits: + memory: "1024Mi" + cpu: "0.50" + autoscaling: + !!merge <<: *autoscaling + enable: false + minReplica: 1 + maxReplica: 1 + envConfig: + !!merge <<: *envConfig + CRONJOB_REPORTGENERATION_ENABLED: 'true' + CRONJOB_REPORTGENERATION_DAILY_SCHEDULE: '00 00 00 * * *' + CRONJOB_REPORTGENERATION_WEEKLY_SCHEDULE: '00 00 07 * * 1' + CRONJOB_REPORTGENERATION_MONTHLY_SCHEDULE: '00 30 07 1 * *' + envSecret: + !!merge <<: *envSecret diff --git a/openapi/openapi.json b/openapi/openapi.json index 3df88f4..48d39e0 100644 --- a/openapi/openapi.json +++ b/openapi/openapi.json @@ -4,7 +4,7 @@ "description": "A service that permits to handle converted WISP requests for technical support", "termsOfService": "https://www.pagopa.gov.it/", "title": "wisp-converter-technical-support", - "version": "0.3.2" + "version": "0.3.2-7-feat-migrate-report-generator" }, "servers": [ { diff --git a/pom.xml b/pom.xml index 8135624..8c5991f 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ it.gov.pagopa wisp-converter-technical-support - 0.3.2 + 0.3.2-7-feat-migrate-report-generator pagoPA WISP Converter Technical support A service that permits to handle converted WISP requests for technical support diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/Application.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/Application.java index 4002210..4f4569a 100644 --- a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/Application.java +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/Application.java @@ -1,9 +1,11 @@ -package it.gov.pagopa.wispconverter.technicalsupport; // TODO: refactor the package +package it.gov.pagopa.wispconverter.technicalsupport; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication +@EnableScheduling public class Application { public static void main(String[] args) { diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/config/LoggingAspect.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/config/LoggingAspect.java index d48ff09..3a1367f 100644 --- a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/config/LoggingAspect.java +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/config/LoggingAspect.java @@ -1,14 +1,10 @@ package it.gov.pagopa.wispconverter.technicalsupport.config; -import static it.gov.pagopa.wispconverter.technicalsupport.util.CommonUtility.deNull; - -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import javax.annotation.PostConstruct; - +import it.gov.pagopa.wispconverter.technicalsupport.controller.model.generic.ProblemJson; +import it.gov.pagopa.wispconverter.technicalsupport.exception.AppError; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.AfterReturning; @@ -21,11 +17,13 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; -import it.gov.pagopa.wispconverter.technicalsupport.exception.AppError; -import it.gov.pagopa.wispconverter.technicalsupport.model.ProblemJson; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; +import javax.annotation.PostConstruct; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import static it.gov.pagopa.wispconverter.technicalsupport.util.CommonUtility.deNull; @Aspect @@ -33,135 +31,137 @@ @Slf4j public class LoggingAspect { - public static final String START_TIME = "startTime"; - public static final String METHOD = "method"; - public static final String STATUS = "status"; - public static final String CODE = "httpCode"; - public static final String RESPONSE_TIME = "responseTime"; - public static final String FAULT_CODE = "faultCode"; - public static final String FAULT_DETAIL = "faultDetail"; - public static final String REQUEST_ID = "requestId"; - public static final String OPERATION_ID = "operationId"; - public static final String ARGS = "args"; - - final HttpServletRequest httRequest; - - final HttpServletResponse httpResponse; - - @Value("${info.application.name}") - private String name; - - @Value("${info.application.version}") - private String version; - - @Value("${info.properties.environment}") - private String environment; - - public LoggingAspect(HttpServletRequest httRequest, HttpServletResponse httpResponse) { - this.httRequest = httRequest; - this.httpResponse = httpResponse; - } - - private static String getDetail(ResponseEntity result) { - if (result != null && result.getBody() != null && result.getBody().getDetail() != null) { - return result.getBody().getDetail(); - } else return AppError.UNKNOWN.getDetails(); - } - - private static String getTitle(ResponseEntity result) { - if (result != null && result.getBody() != null && result.getBody().getTitle() != null) { - return result.getBody().getTitle(); - } else return AppError.UNKNOWN.getTitle(); - } - - public static String getExecutionTime() { - String startTime = MDC.get(START_TIME); - if (startTime != null) { - long endTime = System.currentTimeMillis(); - long executionTime = endTime - Long.parseLong(startTime); - return String.valueOf(executionTime); + public static final String START_TIME = "startTime"; + public static final String METHOD = "method"; + public static final String STATUS = "status"; + public static final String CODE = "httpCode"; + public static final String RESPONSE_TIME = "responseTime"; + public static final String FAULT_CODE = "faultCode"; + public static final String FAULT_DETAIL = "faultDetail"; + public static final String REQUEST_ID = "requestId"; + public static final String OPERATION_ID = "operationId"; + public static final String ARGS = "args"; + + final HttpServletRequest httRequest; + + final HttpServletResponse httpResponse; + + @Value("${info.application.name}") + private String name; + + @Value("${info.application.version}") + private String version; + + @Value("${info.properties.environment}") + private String environment; + + public LoggingAspect(HttpServletRequest httRequest, HttpServletResponse httpResponse) { + this.httRequest = httRequest; + this.httpResponse = httpResponse; } - return "-"; - } - - private static Map getParams(ProceedingJoinPoint joinPoint) { - MethodSignature signature = (MethodSignature) joinPoint.getSignature(); - Method method = signature.getMethod(); - Map params = new HashMap<>(); - int i = 0; - for (var parameter : method.getParameters()) { - var paramName = parameter.getName(); - var arg = joinPoint.getArgs()[i++]; - params.put(paramName, deNull(arg)); + + private static String getDetail(ResponseEntity result) { + if (result != null && result.getBody() != null && result.getBody().getDetail() != null) { + return result.getBody().getDetail(); + } else return AppError.UNKNOWN.getDetails(); } - return params; - } - - @Pointcut("@within(org.springframework.web.bind.annotation.RestController)") - public void restController() { - // all rest controllers - } - - @Pointcut("@within(org.springframework.stereotype.Repository)") - public void repository() { - // all repository methods - } - - @Pointcut("@within(org.springframework.stereotype.Service)") - public void service() { - // all service methods - } - - /** Log essential info of application during the startup. */ - @PostConstruct - public void logStartup() { - log.info("-> Starting {} version {} - environment {}", name, version, environment); - } - - @Around(value = "restController()") - public Object logApiInvocation(ProceedingJoinPoint joinPoint) throws Throwable { - MDC.put(METHOD, joinPoint.getSignature().getName()); - MDC.put(START_TIME, String.valueOf(System.currentTimeMillis())); - MDC.put(OPERATION_ID, UUID.randomUUID().toString()); - if (MDC.get(REQUEST_ID) == null) { - var requestId = UUID.randomUUID().toString(); - MDC.put(REQUEST_ID, requestId); + + private static String getTitle(ResponseEntity result) { + if (result != null && result.getBody() != null && result.getBody().getTitle() != null) { + return result.getBody().getTitle(); + } else return AppError.UNKNOWN.getTitle(); + } + + public static String getExecutionTime() { + String startTime = MDC.get(START_TIME); + if (startTime != null) { + long endTime = System.currentTimeMillis(); + long executionTime = endTime - Long.parseLong(startTime); + return String.valueOf(executionTime); + } + return "-"; + } + + private static Map getParams(ProceedingJoinPoint joinPoint) { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + Map params = new HashMap<>(); + int i = 0; + for (var parameter : method.getParameters()) { + var paramName = parameter.getName(); + var arg = joinPoint.getArgs()[i++]; + params.put(paramName, deNull(arg)); + } + return params; + } + + @Pointcut("@within(org.springframework.web.bind.annotation.RestController)") + public void restController() { + // all rest controllers + } + + @Pointcut("@within(org.springframework.stereotype.Repository)") + public void repository() { + // all repository methods + } + + @Pointcut("@within(org.springframework.stereotype.Service)") + public void service() { + // all service methods + } + + /** + * Log essential info of application during the startup. + */ + @PostConstruct + public void logStartup() { + log.info("-> Starting {} version {} - environment {}", name, version, environment); + } + + @Around(value = "restController()") + public Object logApiInvocation(ProceedingJoinPoint joinPoint) throws Throwable { + MDC.put(METHOD, joinPoint.getSignature().getName()); + MDC.put(START_TIME, String.valueOf(System.currentTimeMillis())); + MDC.put(OPERATION_ID, UUID.randomUUID().toString()); + if (MDC.get(REQUEST_ID) == null) { + var requestId = UUID.randomUUID().toString(); + MDC.put(REQUEST_ID, requestId); + } + Map params = getParams(joinPoint); + MDC.put(ARGS, params.toString()); + + log.debug("Invoking API operation {} - args: {}", joinPoint.getSignature().getName(), params); + + Object result = joinPoint.proceed(); + + MDC.put(STATUS, "OK"); + MDC.put(CODE, String.valueOf(httpResponse.getStatus())); + MDC.put(RESPONSE_TIME, getExecutionTime()); + log.info("Successful API operation {} - result: {}", joinPoint.getSignature().getName(), result); + MDC.remove(STATUS); + MDC.remove(CODE); + MDC.remove(RESPONSE_TIME); + MDC.remove(START_TIME); + return result; + } + + @AfterReturning(value = "execution(* *..exception.ErrorHandler.*(..))", returning = "result") + public void trowingApiInvocation(JoinPoint joinPoint, ResponseEntity result) { + MDC.put(STATUS, "KO"); + MDC.put(CODE, String.valueOf(result.getStatusCode().value())); + MDC.put(RESPONSE_TIME, getExecutionTime()); + MDC.put(FAULT_CODE, getTitle(result)); + MDC.put(FAULT_DETAIL, getDetail(result)); + log.info("Failed API operation {} - error: {}", MDC.get(METHOD), result); + MDC.clear(); + } + + @Around(value = "repository() || service()") + public Object logTrace(ProceedingJoinPoint joinPoint) throws Throwable { + Map params = getParams(joinPoint); + log.debug("Call method {} - args: {}", joinPoint.getSignature().toShortString(), params); + Object result = joinPoint.proceed(); + log.debug("Return method {} - result: {}", joinPoint.getSignature().toShortString(), result); + return result; } - Map params = getParams(joinPoint); - MDC.put(ARGS, params.toString()); - - log.debug("Invoking API operation {} - args: {}", joinPoint.getSignature().getName(), params); - - Object result = joinPoint.proceed(); - - MDC.put(STATUS, "OK"); - MDC.put(CODE, String.valueOf(httpResponse.getStatus())); - MDC.put(RESPONSE_TIME, getExecutionTime()); - log.info("Successful API operation {} - result: {}", joinPoint.getSignature().getName(), result); - MDC.remove(STATUS); - MDC.remove(CODE); - MDC.remove(RESPONSE_TIME); - MDC.remove(START_TIME); - return result; - } - - @AfterReturning(value = "execution(* *..exception.ErrorHandler.*(..))", returning = "result") - public void trowingApiInvocation(JoinPoint joinPoint, ResponseEntity result) { - MDC.put(STATUS, "KO"); - MDC.put(CODE, String.valueOf(result.getStatusCode().value())); - MDC.put(RESPONSE_TIME, getExecutionTime()); - MDC.put(FAULT_CODE, getTitle(result)); - MDC.put(FAULT_DETAIL, getDetail(result)); - log.info("Failed API operation {} - error: {}", MDC.get(METHOD), result); - MDC.clear(); - } - - @Around(value = "repository() || service()") - public Object logTrace(ProceedingJoinPoint joinPoint) throws Throwable { - Map params = getParams(joinPoint); - log.debug("Call method {} - args: {}", joinPoint.getSignature().toShortString(), params); - Object result = joinPoint.proceed(); - log.debug("Return method {} - result: {}", joinPoint.getSignature().toShortString(), result); - return result; - } } diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/config/WebMvcConfiguration.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/config/WebMvcConfiguration.java index b4788e9..41e75be 100644 --- a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/config/WebMvcConfiguration.java +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/config/WebMvcConfiguration.java @@ -1,7 +1,7 @@ package it.gov.pagopa.wispconverter.technicalsupport.config; import com.fasterxml.jackson.databind.ObjectMapper; -import it.gov.pagopa.wispconverter.technicalsupport.model.AppCorsConfiguration; +import it.gov.pagopa.wispconverter.technicalsupport.controller.model.generic.AppCorsConfiguration; import lombok.SneakyThrows; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/controller/ReportGenerationController.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/controller/ReportGenerationController.java new file mode 100644 index 0000000..13fad1f --- /dev/null +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/controller/ReportGenerationController.java @@ -0,0 +1,69 @@ +package it.gov.pagopa.wispconverter.technicalsupport.controller; + +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +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.ReEventResponse; +import it.gov.pagopa.wispconverter.technicalsupport.service.ReportGenerationService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/reports") +@Validated +@RequiredArgsConstructor +@Tag(name = "Report Generation", description = "API for execute report generation on demand") +public class ReportGenerationController { + + private final ReportGenerationService reportGenerationService; + + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Successfully retrieved event", + content = @Content(schema = @Schema(implementation = ReEventResponse.class))) + }) + @PostMapping(value = "/daily", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity generateDailyReport( + @RequestParam(name = "day") @Schema(example = "2024-01-01", description = "The day on which the report will be generated (in yyyy-MM-dd)") String date) { + + reportGenerationService.generateDailyReport(date); + return ResponseEntity.ok("Report generation completed!"); + } + + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Successfully retrieved event", + content = @Content(schema = @Schema(implementation = ReEventResponse.class))) + }) + @PostMapping(value = "/weekly", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity generateWeeklyReport( + @RequestParam(name = "day") @Schema(example = "2024-01-01", description = "The day on which it is calculated the previous day and from the weekly report will be generated (in yyyy-MM-dd)") String date) { + + reportGenerationService.generateWeeklyReport(date); + return ResponseEntity.ok("Report generation completed!"); + } + + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "Successfully retrieved event", + content = @Content(schema = @Schema(implementation = ReEventResponse.class))) + }) + @PostMapping(value = "/monthly", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity generateMonthlyReport( + @RequestParam(name = "day") @Schema(example = "2024-01-01", description = "The day on which it is calculated the previous day and from the monthly report will be generated (in yyyy-MM-dd)") String date) { + + reportGenerationService.generateMonthlyReport(date); + return ResponseEntity.ok("Report generation completed!"); + } +} diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/controller/mapper/ReEventDataExplorerMapper.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/controller/mapper/ReEventDataExplorerMapper.java index f97b581..0dfd909 100644 --- a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/controller/mapper/ReEventDataExplorerMapper.java +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/controller/mapper/ReEventDataExplorerMapper.java @@ -3,7 +3,7 @@ import com.microsoft.azure.kusto.data.KustoResultSetTable; import it.gov.pagopa.wispconverter.technicalsupport.controller.model.EventCategory; import it.gov.pagopa.wispconverter.technicalsupport.controller.model.ReEvent; -import it.gov.pagopa.wispconverter.technicalsupport.repository.model.ReEventDataExplorerEntity; +import it.gov.pagopa.wispconverter.technicalsupport.repository.model.re.ReEventDataExplorerEntity; import it.gov.pagopa.wispconverter.technicalsupport.util.CommonUtility; import org.mapstruct.Mapper; import org.mapstruct.ReportingPolicy; diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/controller/mapper/ReEventMapper.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/controller/mapper/ReEventMapper.java index 8d418af..0b76345 100644 --- a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/controller/mapper/ReEventMapper.java +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/controller/mapper/ReEventMapper.java @@ -1,7 +1,7 @@ package it.gov.pagopa.wispconverter.technicalsupport.controller.mapper; import it.gov.pagopa.wispconverter.technicalsupport.controller.model.ReEvent; -import it.gov.pagopa.wispconverter.technicalsupport.repository.model.ReEventEntity; +import it.gov.pagopa.wispconverter.technicalsupport.repository.model.re.ReEventEntity; import it.gov.pagopa.wispconverter.technicalsupport.util.Constants; import org.mapstruct.*; diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/controller/mapper/ReportMapper.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/controller/mapper/ReportMapper.java new file mode 100644 index 0000000..5fe1838 --- /dev/null +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/controller/mapper/ReportMapper.java @@ -0,0 +1,79 @@ +package it.gov.pagopa.wispconverter.technicalsupport.controller.mapper; + +import it.gov.pagopa.wispconverter.technicalsupport.repository.model.report.*; +import it.gov.pagopa.wispconverter.technicalsupport.service.model.report.NotCompletedRPTStatistic; +import it.gov.pagopa.wispconverter.technicalsupport.service.model.report.RPTStatistic; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +import java.util.Map; + +@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) +public abstract class ReportMapper { + + public ReportEntity toEntity(RPTStatistic dto) { + Map notCompletedTriggeredPrimitives = dto.getNotCompletedTriggeredPrimitivesByStatus(); + NotCompletedRPTStatistic notCompletedRPTs = dto.getNotCompletedRPTs(); + + ReportPaymentEntity reportPayments = ReportPaymentEntity.builder() + .totalOnNdp(dto.getTotalTriggerPrimitivesOnNdp()) + .totalOnWisp(dto.getTotalTriggerPrimitivesOnWisp()) + .triggerPrimitives(ReportTriggeredPrimitivesEntity.builder() + .totalCarts(dto.getTotalRPTCarts()) + .totalNoCarts(dto.getTotalRPTSingles()) + .cartsCompleted(dto.getCompletedRPTCarts().size()) + .noCartsCompleted(dto.getCompletedRPTSingles().size()) + .allNotCompleted(NotCompletedTriggerPrimitivesEntity.builder() + .rptTimeoutTrigger(notCompletedTriggeredPrimitives.getOrDefault("rpt_timeout_trigger", 0)) + .redirect(notCompletedTriggeredPrimitives.getOrDefault("redirect", 0)) + .ecommerceHangTimeoutTrigger(notCompletedTriggeredPrimitives.getOrDefault("ecommerce_hang_timeout_trigger", 0)) + .paymentTokenTimeoutTrigger(notCompletedTriggeredPrimitives.getOrDefault("payment_token_timeout_trigger", 0)) + .receiptKo(notCompletedTriggeredPrimitives.getOrDefault("receipt_ko", 0)) + .noState(notCompletedTriggeredPrimitives.getOrDefault("no_state", 0)) + .build()) + .build()) + .build(); + ReportReceiptEntity reportReceipts = ReportReceiptEntity.builder() + .completed(ReportSentReceiptEntity.builder() + .withOkReceipts(dto.getReceiptOkSent()) + .withKoReceipts(dto.getReceiptKoSent()) + .build()) + .notCompleted(ReportNotSentReceiptEntity.builder() + .rejected(ReportNotSentReceiptStatsEntity.builder() + .receiptOkCount(notCompletedRPTs.getRejected().getReceiptOkCount()) + .receiptKoCount(notCompletedRPTs.getRejected().getReceiptKoCount()) + .receipts(notCompletedRPTs.getRejected().getReceiptsIds()) + .build()) + .notSentEndRetry(ReportNotSentReceiptStatsEntity.builder() + .receiptOkCount(notCompletedRPTs.getNotSentEndRetry().getReceiptOkCount()) + .receiptKoCount(notCompletedRPTs.getNotSentEndRetry().getReceiptKoCount()) + .receipts(notCompletedRPTs.getNotSentEndRetry().getReceiptsIds()) + .build()) + .ongoing(ReportNotSentReceiptStatsEntity.builder() + .receiptOkCount(notCompletedRPTs.getOngoing().getReceiptOkCount()) + .receiptKoCount(notCompletedRPTs.getOngoing().getReceiptKoCount()) + .receipts(notCompletedRPTs.getOngoing().getReceiptsIds()) + .build()) + .scheduled(ReportNotSentReceiptStatsEntity.builder() + .receiptOkCount(notCompletedRPTs.getSendingOrScheduled().getReceiptOkCount()) + .receiptKoCount(notCompletedRPTs.getSendingOrScheduled().getReceiptKoCount()) + .receipts(notCompletedRPTs.getSendingOrScheduled().getReceiptsIds()) + .build()) + .neverSent(ReportNotSentReceiptStatsEntity.builder() + .receiptOkCount(notCompletedRPTs.getNeverSent().getReceiptOkCount()) + .receiptKoCount(notCompletedRPTs.getNeverSent().getReceiptKoCount()) + .receipts(notCompletedRPTs.getNeverSent().getReceiptsIds()) + .build()) + .build()) + .build(); + + String date = dto.getDate(); + return ReportEntity.builder() + .id(date + "_" + dto.getType().name().toLowerCase()) + .date(date) + .payments(reportPayments) + .receipts(reportReceipts) + .build(); + } + +} diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/model/AppCorsConfiguration.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/controller/model/generic/AppCorsConfiguration.java similarity index 84% rename from src/main/java/it/gov/pagopa/wispconverter/technicalsupport/model/AppCorsConfiguration.java rename to src/main/java/it/gov/pagopa/wispconverter/technicalsupport/controller/model/generic/AppCorsConfiguration.java index 6d45c01..6514a63 100644 --- a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/model/AppCorsConfiguration.java +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/controller/model/generic/AppCorsConfiguration.java @@ -1,4 +1,4 @@ -package it.gov.pagopa.wispconverter.technicalsupport.model; +package it.gov.pagopa.wispconverter.technicalsupport.controller.model.generic; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/model/AppInfo.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/controller/model/generic/AppInfo.java similarity index 81% rename from src/main/java/it/gov/pagopa/wispconverter/technicalsupport/model/AppInfo.java rename to src/main/java/it/gov/pagopa/wispconverter/technicalsupport/controller/model/generic/AppInfo.java index 584a294..38dea3e 100644 --- a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/model/AppInfo.java +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/controller/model/generic/AppInfo.java @@ -1,4 +1,4 @@ -package it.gov.pagopa.wispconverter.technicalsupport.model; +package it.gov.pagopa.wispconverter.technicalsupport.controller.model.generic; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.*; diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/model/ProblemJson.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/controller/model/generic/ProblemJson.java similarity index 94% rename from src/main/java/it/gov/pagopa/wispconverter/technicalsupport/model/ProblemJson.java rename to src/main/java/it/gov/pagopa/wispconverter/technicalsupport/controller/model/generic/ProblemJson.java index 1d763d6..4695e96 100644 --- a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/model/ProblemJson.java +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/controller/model/generic/ProblemJson.java @@ -1,4 +1,4 @@ -package it.gov.pagopa.wispconverter.technicalsupport.model; +package it.gov.pagopa.wispconverter.technicalsupport.controller.model.generic; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/exception/ErrorHandler.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/exception/ErrorHandler.java index 19c3f94..65de928 100644 --- a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/exception/ErrorHandler.java +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/exception/ErrorHandler.java @@ -1,7 +1,7 @@ package it.gov.pagopa.wispconverter.technicalsupport.exception; -import it.gov.pagopa.wispconverter.technicalsupport.model.ProblemJson; +import it.gov.pagopa.wispconverter.technicalsupport.controller.model.generic.ProblemJson; import jakarta.validation.ConstraintViolationException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.TypeMismatchException; diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/RPTRequestRepository.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/RPTRequestRepository.java new file mode 100644 index 0000000..b705367 --- /dev/null +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/RPTRequestRepository.java @@ -0,0 +1,19 @@ +package it.gov.pagopa.wispconverter.technicalsupport.repository; + + +import com.azure.spring.data.cosmos.repository.CosmosRepository; +import com.azure.spring.data.cosmos.repository.Query; +import it.gov.pagopa.wispconverter.technicalsupport.repository.model.RPTRequestEntity; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface RPTRequestRepository extends CosmosRepository { + + @Query("SELECT c.id, c.primitive " + + "FROM c " + + "WHERE c.PartitionKey = @date") + List findAllByPartitionKeyExcludingPayload(@Param("date") String date); +} \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/RTRepository.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/RTRepository.java index c59c101..5d64c29 100644 --- a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/RTRepository.java +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/RTRepository.java @@ -14,6 +14,10 @@ @Repository public interface RTRepository extends CosmosRepository { + @Query("SELECT c.id, c.receiptStatus, c.receiptType " + + "FROM c " + + "WHERE c.sessionId = @sessionId") + List findStatusInfoBySessionId(@Param("sessionId") String sessionId); @Query("SELECT COUNT(1) AS eventStatusCount, c.receiptStatus " + "FROM c " + diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/ReEventDataExplorerRepository.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/ReEventDataExplorerRepository.java index 41745b3..cd47a07 100644 --- a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/ReEventDataExplorerRepository.java +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/ReEventDataExplorerRepository.java @@ -6,7 +6,7 @@ import it.gov.pagopa.wispconverter.technicalsupport.controller.mapper.ReEventDataExplorerMapper; import it.gov.pagopa.wispconverter.technicalsupport.exception.AppError; import it.gov.pagopa.wispconverter.technicalsupport.exception.AppException; -import it.gov.pagopa.wispconverter.technicalsupport.repository.model.ReEventDataExplorerEntity; +import it.gov.pagopa.wispconverter.technicalsupport.repository.model.re.ReEventDataExplorerEntity; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -42,6 +42,14 @@ public class ReEventDataExplorerRepository { | where tipoEvento contains 'sendRTV2' | where sottoTipoEvento == 'REQ'"""; + public static final String QUERY_FIND_TRIGGER_PRIMITIVE_COUNT = """ + ReEvent + | where insertedTimestamp >= todatetime("%sT00:00:00") + | where insertedTimestamp <= todatetime("%sT23:59:59") + | where tipoEvento == 'nodoInviaRPT' or tipoEvento == 'nodoInviaCarrelloRPT' + | where sottoTipoEvento == 'REQ' + | count"""; + private final ReEventDataExplorerMapper reEventDataExplorerMapper; private final Client kustoClient; @@ -67,6 +75,12 @@ public List findSendRTV2Event(String dateFrom, String return executeQuery(query); } + public Long countTriggerPrimitives(String dateFrom, String dateTo) { + + String query = String.format(QUERY_FIND_TRIGGER_PRIMITIVE_COUNT, dateFrom, dateTo); + return executeQueryForCount(query); + } + public List executeQuery(String query) { List result = new LinkedList<>(); @@ -82,4 +96,20 @@ public List executeQuery(String query) { } return result; } + + public long executeQueryForCount(String query) { + + long result = 0L; + try { + KustoOperationResult response = kustoClient.execute(database, query); + KustoResultSetTable primaryResults = response.getPrimaryResults(); + while (primaryResults.hasNext()) { + primaryResults.next(); + result = primaryResults.getLong(0); + } + } catch (Exception e) { + throw new AppException(AppError.INTERNAL_SERVER_ERROR, e); + } + return result; + } } diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/ReEventExperimentalRepository.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/ReEventExperimentalRepository.java index 3eb038f..f979bb4 100644 --- a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/ReEventExperimentalRepository.java +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/ReEventExperimentalRepository.java @@ -3,7 +3,7 @@ import com.azure.spring.data.cosmos.repository.CosmosRepository; import com.azure.spring.data.cosmos.repository.Query; -import it.gov.pagopa.wispconverter.technicalsupport.repository.model.ReEventEntity; +import it.gov.pagopa.wispconverter.technicalsupport.repository.model.re.ReEventEntity; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/ReEventRepository.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/ReEventRepository.java index 20edd6e..7b3adc7 100644 --- a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/ReEventRepository.java +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/ReEventRepository.java @@ -3,10 +3,11 @@ import com.azure.spring.data.cosmos.repository.CosmosRepository; import com.azure.spring.data.cosmos.repository.Query; -import it.gov.pagopa.wispconverter.technicalsupport.repository.model.ReEventEntity; +import it.gov.pagopa.wispconverter.technicalsupport.repository.model.re.ReEventEntity; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.Set; @Repository @@ -25,8 +26,8 @@ Set findBySessionId(@Param("dateFrom") String dateFrom, @Query("SELECT DISTINCT VALUE c.sessionId " + "FROM c " + "WHERE (c.partitionKey >= @dateFrom AND c.partitionKey <= @dateTo) " + - "AND c.iuv = @iuv " + "AND c.domainId = @organizationId " + + "AND c.iuv = @iuv " + "AND IS_DEFINED(c.sessionId) AND c.sessionId != null") Set findSessionIdByIuvAndDomainId(@Param("dateFrom") String dateFrom, @Param("dateTo") String dateTo, @@ -37,12 +38,18 @@ Set findSessionIdByIuvAndDomainId(@Param("dateFrom") String dateFrom, @Query("SELECT DISTINCT VALUE c.sessionId " + "FROM c " + "WHERE (c.partitionKey >= @dateFrom AND c.partitionKey <= @dateTo) " + - "AND c.noticeNumber = @noticeNumber " + "AND c.domainId = @organizationId " + + "AND c.noticeNumber = @noticeNumber " + "AND IS_DEFINED(c.sessionId) AND c.sessionId != null") Set findSessionIdByNoticeNumberAndDomainId(@Param("dateFrom") String dateFrom, @Param("dateTo") String dateTo, @Param("organizationId") String organizationId, @Param("noticeNumber") String noticeNumber); + @Query("SELECT * " + + "FROM c " + + "WHERE c.sessionId = @sessionId " + + "AND c.status = 'COMMUNICATION_WITH_CREDITOR_INSTITUTION_PROCESSED' " + + "AND c.businessProcess IN ('rpt-timeout-trigger', 'redirect', 'ecommerce-hang-timeout-trigger', 'payment-token-timeout-trigger', 'receipt-ko', 'receipt-ok')") + List findRtTriggerRelatedEventBySessionId(@Param("sessionId") String sessionId); } \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/ReportRepository.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/ReportRepository.java new file mode 100644 index 0000000..f24f84f --- /dev/null +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/ReportRepository.java @@ -0,0 +1,11 @@ +package it.gov.pagopa.wispconverter.technicalsupport.repository; + + +import com.azure.spring.data.cosmos.repository.CosmosRepository; +import it.gov.pagopa.wispconverter.technicalsupport.repository.model.report.ReportEntity; +import org.springframework.stereotype.Repository; + +@Repository +public interface ReportRepository extends CosmosRepository { + +} \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/RPTRequestEntity.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/RPTRequestEntity.java new file mode 100644 index 0000000..6c5de26 --- /dev/null +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/RPTRequestEntity.java @@ -0,0 +1,27 @@ +package it.gov.pagopa.wispconverter.technicalsupport.repository.model; + +import com.azure.spring.data.cosmos.core.mapping.Container; +import com.azure.spring.data.cosmos.core.mapping.PartitionKey; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.data.annotation.Id; + +@Container(containerName = "data") +@Data +@ToString(exclude = "payload") +@EqualsAndHashCode(exclude = "payload") +@Builder(toBuilder = true) +public class RPTRequestEntity { + + @Id + private String id; + + @PartitionKey + private String partitionKey; + + private String primitive; + + private String payload; +} diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/ReEventDataExplorerEntity.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/re/ReEventDataExplorerEntity.java similarity index 98% rename from src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/ReEventDataExplorerEntity.java rename to src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/re/ReEventDataExplorerEntity.java index 0c986b6..83f31c9 100644 --- a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/ReEventDataExplorerEntity.java +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/re/ReEventDataExplorerEntity.java @@ -1,4 +1,4 @@ -package it.gov.pagopa.wispconverter.technicalsupport.repository.model; +package it.gov.pagopa.wispconverter.technicalsupport.repository.model.re; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/ReEventEntity.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/re/ReEventEntity.java similarity index 99% rename from src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/ReEventEntity.java rename to src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/re/ReEventEntity.java index e32aef9..9b0c075 100644 --- a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/ReEventEntity.java +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/re/ReEventEntity.java @@ -1,4 +1,4 @@ -package it.gov.pagopa.wispconverter.technicalsupport.repository.model; +package it.gov.pagopa.wispconverter.technicalsupport.repository.model.re; import com.azure.spring.data.cosmos.core.mapping.Container; import com.azure.spring.data.cosmos.core.mapping.GeneratedValue; diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/report/NotCompletedTriggerPrimitivesEntity.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/report/NotCompletedTriggerPrimitivesEntity.java new file mode 100644 index 0000000..33ab62e --- /dev/null +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/report/NotCompletedTriggerPrimitivesEntity.java @@ -0,0 +1,66 @@ +package it.gov.pagopa.wispconverter.technicalsupport.repository.model.report; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder(toBuilder = true) +@AllArgsConstructor +public class NotCompletedTriggerPrimitivesEntity { + + /** + * Counter for trigger primitives not completed, closed in KO status by process rpt_timeout_trigger + */ + @JsonProperty("rpt_timeout_trigger") + private Integer rptTimeoutTrigger; + + /** + * Counter for trigger primitives not completed, closed in KO status by process redirect + */ + @JsonProperty("redirect") + private Integer redirect; + + /** + * Counter for trigger primitives not completed, closed in KO status by process receipt_ko + */ + @JsonProperty("receipt_ko") + private Integer receiptKo; + + /** + * Counter for trigger primitives not completed, closed in KO status by process payment_token_timeout_trigger + */ + @JsonProperty("payment_token_timeout_trigger") + private Integer paymentTokenTimeoutTrigger; + + /** + * Counter for trigger primitives not completed, closed in KO status by process ecommerce_hang_timeout_trigger + */ + @JsonProperty("ecommerce_hang_timeout_trigger") + private Integer ecommerceHangTimeoutTrigger; + + /** + * Counter for trigger primitives not completed, closed in KO status by some undefined process + */ + @JsonProperty("no_state") + private Integer noState; + + public NotCompletedTriggerPrimitivesEntity() { + this.rptTimeoutTrigger = 0; + this.redirect = 0; + this.receiptKo = 0; + this.paymentTokenTimeoutTrigger = 0; + this.ecommerceHangTimeoutTrigger = 0; + this.noState = 0; + } + + public void merge(NotCompletedTriggerPrimitivesEntity other) { + this.rptTimeoutTrigger += other.rptTimeoutTrigger; + this.redirect += other.redirect; + this.receiptKo += other.receiptKo; + this.paymentTokenTimeoutTrigger += other.paymentTokenTimeoutTrigger; + this.ecommerceHangTimeoutTrigger += other.ecommerceHangTimeoutTrigger; + this.noState += other.noState; + } +} diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/report/ReportEntity.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/report/ReportEntity.java new file mode 100644 index 0000000..ec34ed3 --- /dev/null +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/report/ReportEntity.java @@ -0,0 +1,41 @@ +package it.gov.pagopa.wispconverter.technicalsupport.repository.model.report; + +import com.azure.spring.data.cosmos.core.mapping.Container; +import com.azure.spring.data.cosmos.core.mapping.PartitionKey; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import org.springframework.data.annotation.Id; + +@Container(containerName = "reports") +@Data +@Builder(toBuilder = true) +@AllArgsConstructor +public class ReportEntity { + + @Id + private String id; + + @PartitionKey + private String date; + + /** + * Statistics about trigger primitives + */ + private ReportPaymentEntity payments; + + /** + * Statistics about handled receipts + */ + private ReportReceiptEntity receipts; + + public ReportEntity() { + this.payments = new ReportPaymentEntity(); + this.receipts = new ReportReceiptEntity(); + } + + public void merge(ReportEntity other) { + this.payments.merge(other.payments); + this.receipts.merge(other.receipts); + } +} diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/report/ReportNotSentReceiptEntity.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/report/ReportNotSentReceiptEntity.java new file mode 100644 index 0000000..74c5b8e --- /dev/null +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/report/ReportNotSentReceiptEntity.java @@ -0,0 +1,58 @@ +package it.gov.pagopa.wispconverter.technicalsupport.repository.model.report; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder(toBuilder = true) +@AllArgsConstructor +public class ReportNotSentReceiptEntity { + + /** + * Counter for total not sent receipts, rejected by CI + */ + @JsonProperty("rejected") + private ReportNotSentReceiptStatsEntity rejected; + + /** + * Counter for total not sent receipts, not sent for end retry + */ + @JsonProperty("not_sent_end_retry") + private ReportNotSentReceiptStatsEntity notSentEndRetry; + + /** + * Counter for total not sent receipts, re-scheduled for retry + */ + @JsonProperty("scheduled") + private ReportNotSentReceiptStatsEntity scheduled; + + /** + * Counter for total not sent receipts, for ongoing payment + */ + @JsonProperty("ongoing") + private ReportNotSentReceiptStatsEntity ongoing; + + /** + * Counter for total not sent receipts, never sent to CI + */ + @JsonProperty("never_sent") + private ReportNotSentReceiptStatsEntity neverSent; + + public ReportNotSentReceiptEntity() { + this.rejected = new ReportNotSentReceiptStatsEntity(); + this.notSentEndRetry = new ReportNotSentReceiptStatsEntity(); + this.scheduled = new ReportNotSentReceiptStatsEntity(); + this.ongoing = new ReportNotSentReceiptStatsEntity(); + this.neverSent = new ReportNotSentReceiptStatsEntity(); + } + + public void merge(ReportNotSentReceiptEntity other) { + this.rejected.merge(other.rejected); + this.notSentEndRetry.merge(other.notSentEndRetry); + this.scheduled.merge(other.scheduled); + this.ongoing.merge(other.ongoing); + this.neverSent.merge(other.neverSent); + } +} diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/report/ReportNotSentReceiptStatsEntity.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/report/ReportNotSentReceiptStatsEntity.java new file mode 100644 index 0000000..6757ed2 --- /dev/null +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/report/ReportNotSentReceiptStatsEntity.java @@ -0,0 +1,45 @@ +package it.gov.pagopa.wispconverter.technicalsupport.repository.model.report; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +import java.util.HashSet; +import java.util.Set; + +@Data +@Builder(toBuilder = true) +@AllArgsConstructor +public class ReportNotSentReceiptStatsEntity { + + /** + * Counter for total not sent OK receipts + */ + @JsonProperty("receipt_ok_count") + private Integer receiptOkCount; + + /** + * Counter for total not sent KO receipts + */ + @JsonProperty("receipt_ko_count") + private Integer receiptKoCount; + + /** + * List of identifier of not sent receipts + */ + @JsonProperty("receipts") + private Set receipts; + + public ReportNotSentReceiptStatsEntity() { + this.receiptOkCount = 0; + this.receiptKoCount = 0; + this.receipts = new HashSet<>(); + } + + public void merge(ReportNotSentReceiptStatsEntity other) { + this.receiptOkCount += other.receiptOkCount; + this.receiptKoCount += other.receiptKoCount; + this.receipts.addAll(other.receipts); + } +} diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/report/ReportPaymentEntity.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/report/ReportPaymentEntity.java new file mode 100644 index 0000000..c602b12 --- /dev/null +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/report/ReportPaymentEntity.java @@ -0,0 +1,42 @@ +package it.gov.pagopa.wispconverter.technicalsupport.repository.model.report; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder(toBuilder = true) +@AllArgsConstructor +public class ReportPaymentEntity { + + /** + * Counter for total nodoInviaRPT and nodoInviaCarrelloRPT handled by NdP + */ + @JsonProperty("total_on_ndp") + private Long totalOnNdp; + + /** + * Counter for total nodoInviaRPT and nodoInviaCarrelloRPT handled by D-WISP + */ + @JsonProperty("total_on_wisp") + private Long totalOnWisp; + + /** + * Statistics for cataloguing success and failure on nodoInviaRPT and nodoInviaCarrelloRPT handled by D-WISP + */ + @JsonProperty("trigger_primitives") + private ReportTriggeredPrimitivesEntity triggerPrimitives; + + public ReportPaymentEntity() { + this.totalOnNdp = 0L; + this.totalOnWisp = 0L; + this.triggerPrimitives = new ReportTriggeredPrimitivesEntity(); + } + + public void merge(ReportPaymentEntity other) { + this.totalOnNdp += other.totalOnNdp; + this.totalOnWisp += other.totalOnWisp; + this.triggerPrimitives.merge(other.triggerPrimitives); + } +} diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/report/ReportReceiptEntity.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/report/ReportReceiptEntity.java new file mode 100644 index 0000000..8b4d8c5 --- /dev/null +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/report/ReportReceiptEntity.java @@ -0,0 +1,34 @@ +package it.gov.pagopa.wispconverter.technicalsupport.repository.model.report; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder(toBuilder = true) +@AllArgsConstructor +public class ReportReceiptEntity { + + /** + * Statistics about OK or KO receipt correctly sent + */ + @JsonProperty("completed") + private ReportSentReceiptEntity completed; + + /** + * Statistics about OK or KO receipt not correctly sent + */ + @JsonProperty("not_completed") + private ReportNotSentReceiptEntity notCompleted; + + public ReportReceiptEntity() { + this.completed = new ReportSentReceiptEntity(); + this.notCompleted = new ReportNotSentReceiptEntity(); + } + + public void merge(ReportReceiptEntity other) { + this.completed.merge(other.completed); + this.notCompleted.merge(other.notCompleted); + } +} diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/report/ReportSentReceiptEntity.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/report/ReportSentReceiptEntity.java new file mode 100644 index 0000000..bd665f1 --- /dev/null +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/report/ReportSentReceiptEntity.java @@ -0,0 +1,34 @@ +package it.gov.pagopa.wispconverter.technicalsupport.repository.model.report; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder(toBuilder = true) +@AllArgsConstructor +public class ReportSentReceiptEntity { + + /** + * Counter for total sent receipts of type OK + */ + @JsonProperty("with_ok_receipts") + private Integer withOkReceipts; + + /** + * Counter for total sent receipts of type KO + */ + @JsonProperty("with_ko_receipts") + private Integer withKoReceipts; + + public ReportSentReceiptEntity() { + this.withOkReceipts = 0; + this.withKoReceipts = 0; + } + + public void merge(ReportSentReceiptEntity other) { + this.withOkReceipts += other.withOkReceipts; + this.withKoReceipts += other.withKoReceipts; + } +} diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/report/ReportTriggeredPrimitivesEntity.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/report/ReportTriggeredPrimitivesEntity.java new file mode 100644 index 0000000..7763db3 --- /dev/null +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/repository/model/report/ReportTriggeredPrimitivesEntity.java @@ -0,0 +1,59 @@ +package it.gov.pagopa.wispconverter.technicalsupport.repository.model.report; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder(toBuilder = true) +@AllArgsConstructor +public class ReportTriggeredPrimitivesEntity { + + /** + * Counter for all nodoInviaCarrelloRPT handled by D-WISP + */ + @JsonProperty("total_carts") + private Long totalCarts; + + /** + * Counter for all nodoInviaRPT handled by D-WISP + */ + @JsonProperty("total_no_carts") + private Long totalNoCarts; + + /** + * Counter for completed nodoInviaCarrelloRPT + */ + @JsonProperty("carts_completed") + private Integer cartsCompleted; + + /** + * Counter for completed nodoInviaRPT + */ + @JsonProperty("no_carts_completed") + private Integer noCartsCompleted; + + /** + * Counter for all not completed triggered primitives, catalogued by error + */ + @JsonProperty("all_not_completed") + private NotCompletedTriggerPrimitivesEntity allNotCompleted; + + public ReportTriggeredPrimitivesEntity() { + this.totalCarts = 0L; + this.totalNoCarts = 0L; + this.cartsCompleted = 0; + this.noCartsCompleted = 0; + this.allNotCompleted = new NotCompletedTriggerPrimitivesEntity(); + } + + public void merge(ReportTriggeredPrimitivesEntity other) { + + this.totalCarts += other.totalCarts; + this.totalNoCarts += other.totalNoCarts; + this.cartsCompleted += other.cartsCompleted; + this.noCartsCompleted += other.noCartsCompleted; + this.allNotCompleted.merge(other.allNotCompleted); + } +} diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/scheduling/ReportGeneration.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/scheduling/ReportGeneration.java new file mode 100644 index 0000000..19b877e --- /dev/null +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/scheduling/ReportGeneration.java @@ -0,0 +1,52 @@ +package it.gov.pagopa.wispconverter.technicalsupport.scheduling; + +import it.gov.pagopa.wispconverter.technicalsupport.service.ReportGenerationService; +import it.gov.pagopa.wispconverter.technicalsupport.util.CommonUtility; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; + +@Component +@Slf4j +@ConditionalOnProperty(name = "cron.job.schedule.report-generation.enabled", matchIfMissing = false) +public class ReportGeneration { + + private final ReportGenerationService reportGenerationService; + + @Autowired + public ReportGeneration(ReportGenerationService reportGenerationService) { + this.reportGenerationService = reportGenerationService; + } + + + @Scheduled(cron = "${cron.job.schedule.report-generation.daily.cron}") + public void generateDailyReport() { + + log.info("[Report Generation][Trigg] Triggered cron invocation for daily generation."); + LocalDate yesterday = LocalDate.now().minusDays(1); + String dayForReport = CommonUtility.partitionKeyFromInstant(yesterday); + this.reportGenerationService.generateDailyReport(dayForReport); + } + + @Scheduled(cron = "${cron.job.schedule.report-generation.weekly.cron}") + public void generateWeeklyReport() { + + log.info("[Report Generation][Trigg] Triggered cron invocation for weekly generation."); + LocalDate yesterday = LocalDate.now().minusDays(1); + String dayForReport = CommonUtility.partitionKeyFromInstant(yesterday); + this.reportGenerationService.generateWeeklyReport(dayForReport); + } + + @Scheduled(cron = "${cron.job.schedule.report-generation.monthly.cron}") + public void generateMonthlyReport() { + + log.info("[Report Generation][Trigg] Triggered cron invocation for monthly generation."); + LocalDate yesterday = LocalDate.now().minusDays(1); + String dayForReport = CommonUtility.partitionKeyFromInstant(yesterday); + this.reportGenerationService.generateMonthlyReport(dayForReport); + } +} diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/service/EnhancedFeaturesService.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/service/EnhancedFeaturesService.java index 56db6cc..8d219c7 100644 --- a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/service/EnhancedFeaturesService.java +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/service/EnhancedFeaturesService.java @@ -12,8 +12,8 @@ import it.gov.pagopa.wispconverter.technicalsupport.repository.ReEventExperimentalRepository; import it.gov.pagopa.wispconverter.technicalsupport.repository.model.RTEntity; import it.gov.pagopa.wispconverter.technicalsupport.repository.model.RTGroupedByStatusEntity; -import it.gov.pagopa.wispconverter.technicalsupport.repository.model.ReEventDataExplorerEntity; -import it.gov.pagopa.wispconverter.technicalsupport.repository.model.ReEventEntity; +import it.gov.pagopa.wispconverter.technicalsupport.repository.model.re.ReEventDataExplorerEntity; +import it.gov.pagopa.wispconverter.technicalsupport.repository.model.re.ReEventEntity; import it.gov.pagopa.wispconverter.technicalsupport.service.model.PaymentUniqueID; import it.gov.pagopa.wispconverter.technicalsupport.util.CommonUtility; import it.gov.pagopa.wispconverter.technicalsupport.util.Constants; diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/service/ReService.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/service/ReService.java index e09ddc8..6105e56 100644 --- a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/service/ReService.java +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/service/ReService.java @@ -3,7 +3,7 @@ import it.gov.pagopa.wispconverter.technicalsupport.controller.mapper.ReEventMapper; import it.gov.pagopa.wispconverter.technicalsupport.controller.model.ReEvent; import it.gov.pagopa.wispconverter.technicalsupport.repository.ReEventRepository; -import it.gov.pagopa.wispconverter.technicalsupport.repository.model.ReEventEntity; +import it.gov.pagopa.wispconverter.technicalsupport.repository.model.re.ReEventEntity; import it.gov.pagopa.wispconverter.technicalsupport.util.CommonUtility; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/service/ReportGenerationService.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/service/ReportGenerationService.java new file mode 100644 index 0000000..fbc099f --- /dev/null +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/service/ReportGenerationService.java @@ -0,0 +1,231 @@ +package it.gov.pagopa.wispconverter.technicalsupport.service; + +import com.azure.cosmos.models.PartitionKey; +import it.gov.pagopa.wispconverter.technicalsupport.controller.mapper.ReportMapper; +import it.gov.pagopa.wispconverter.technicalsupport.repository.*; +import it.gov.pagopa.wispconverter.technicalsupport.repository.model.RPTRequestEntity; +import it.gov.pagopa.wispconverter.technicalsupport.repository.model.RTEntity; +import it.gov.pagopa.wispconverter.technicalsupport.repository.model.re.ReEventEntity; +import it.gov.pagopa.wispconverter.technicalsupport.repository.model.report.ReportEntity; +import it.gov.pagopa.wispconverter.technicalsupport.service.model.report.RPTStatistic; +import it.gov.pagopa.wispconverter.technicalsupport.service.model.report.RPTStatisticDetail; +import it.gov.pagopa.wispconverter.technicalsupport.service.model.report.ReportType; +import it.gov.pagopa.wispconverter.technicalsupport.util.CommonUtility; +import it.gov.pagopa.wispconverter.technicalsupport.util.Constants; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.util.Pair; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +@Service +@Slf4j +@RequiredArgsConstructor +public class ReportGenerationService { + + private final ReportMapper reportMapper; + private final ReEventDataExplorerRepository dataExplorerRepository; + private final RPTRequestRepository rptRequestRepository; + private final RTRepository rtRepository; + private final ReEventRepository reEventRepository; + private final ReportRepository reportRepository; + + private static void setStatisticsForReceipt(RTEntity receipt, + RPTStatistic rptStats, + Pair triggerPrimitivePair, + Set sessionIdsInError) { + + RPTStatisticDetail statistics; + + String sessionId = triggerPrimitivePair.getFirst(); + String primitiveType = triggerPrimitivePair.getSecond(); + + String receiptStatus = receipt.getReceiptStatus(); + String receiptId = receipt.getId(); + String receiptType = receipt.getReceiptType(); + + switch (receiptStatus) { + + // The receipt was correctly sent (either by direct send or by retry), add it as completed + case "SENT": + if ("OK".equals(receiptType)) { + rptStats.addReceiptOkSent(); + // Include sessionId for define the counter of completed RPT carts/single RPT + if (Constants.STEP_TRIGGER_PRIMITIVE_NODOINVIARPT.equals(primitiveType)) { + rptStats.addCompletedRPTSingles(sessionId); + } else { + rptStats.addCompletedRPTCarts(sessionId); + } + } else { + sessionIdsInError.add(sessionId); + rptStats.addReceiptKoSent(); + } + break; + case "SENT_REJECTED_BY_EC": + statistics = rptStats.getNotCompletedRPTs().getRejected(); + updateStatsForOkOrKo(rptStats, sessionIdsInError, receiptType, statistics, receiptId, primitiveType, sessionId); + break; + case "NOT_SENT": + statistics = rptStats.getNotCompletedRPTs().getNotSentEndRetry(); + updateStatsForOkOrKo(rptStats, sessionIdsInError, receiptType, statistics, receiptId, primitiveType, sessionId); + break; + case "SCHEDULED", "SENDING": + statistics = rptStats.getNotCompletedRPTs().getSendingOrScheduled(); + updateStatsForOkOrKo(rptStats, sessionIdsInError, receiptType, statistics, receiptId, primitiveType, sessionId); + break; + case "REDIRECT", "PAYING": + sessionIdsInError.add(sessionId); + rptStats.getNotCompletedRPTs().getOngoing().addAsKoReceipt(receiptId); + break; + default: + statistics = rptStats.getNotCompletedRPTs().getNeverSent(); + if ("OK".equals(receiptType)) { + statistics.addAsOkReceipt(receiptId); + } else { + sessionIdsInError.add(sessionId); + statistics.addAsKoReceipt(receiptId); + rptStats.addNotCompletedTriggeredPrimitives("no_state"); + } + break; + } + } + + private static void updateStatsForOkOrKo(RPTStatistic rptStats, + Set sessionIdsInError, + String receiptType, + RPTStatisticDetail statistics, + String receiptId, + String primitiveType, + String sessionId) { + + if ("OK".equals(receiptType)) { + statistics.addAsOkReceipt(receiptId); + // Include sessionId for define the counter of completed RPT carts/single RPT + if (Constants.STEP_TRIGGER_PRIMITIVE_NODOINVIARPT.equals(primitiveType)) { + rptStats.addCompletedRPTSingles(sessionId); + } else { + rptStats.addCompletedRPTCarts(sessionId); + } + } else { + sessionIdsInError.add(sessionId); + statistics.addAsKoReceipt(receiptId); + } + } + + public void generateDailyReport(String day) { + + log.info("[Report Generation][Start] Started report generation for {}.", day); + RPTStatistic rptStats = new RPTStatistic(); + rptStats.setDate(day); + rptStats.setType(ReportType.DAILY); + + log.info("[Report Generation][Step ] Executing count for event from Data Explorer on NdP..."); + long numberOfTriggerPrimitivesOnNdp = dataExplorerRepository.countTriggerPrimitives(day, day); + rptStats.setTotalTriggerPrimitivesOnNdp(numberOfTriggerPrimitivesOnNdp); + log.info("[Report Generation][Step ] Executed count for event from Data Explorer on NdP! Retrieved count: [{}]", numberOfTriggerPrimitivesOnNdp); + + log.info("[Report Generation][Step ] Executing count for trigger primitives on WISP Dismantling..."); + List triggerPrimitives = rptRequestRepository.findAllByPartitionKeyExcludingPayload(day); + Set> allSessionIds = triggerPrimitives.stream() + .map(triggerPrimitive -> Pair.of(triggerPrimitive.getId(), triggerPrimitive.getPrimitive())) + .collect(Collectors.toUnmodifiableSet()); + rptStats.setTotalTriggerPrimitivesOnWisp(allSessionIds.size()); + log.info("[Report Generation][Step ] Executed count for trigger primitives on WISP Dismantling! Retrieved count: [{}]", rptStats.getTotalTriggerPrimitivesOnWisp()); + + + log.info("[Report Generation][Step ] Executing extraction of statistics for each retrieved trigger primitive..."); + int numberOfPrimitives = triggerPrimitives.size(); + int alreadyAnalyzedCount = 0; + Set sessionIdsInError = new HashSet<>(); + + for (Pair triggerPrimitivePair : allSessionIds) { + + // Showing "already analyzed" count for better tracking process + alreadyAnalyzedCount++; + if (alreadyAnalyzedCount % 5000 == 0) { + float percentageAnalyzed = CommonUtility.safeDivide(alreadyAnalyzedCount * 100f, numberOfPrimitives); + log.info("[Report Generation][Step ] At [{}] preliminary checks on receipts were generated on [{}/{}] triggered primitives ({}%)...", LocalDateTime.now(), alreadyAnalyzedCount, numberOfPrimitives, percentageAnalyzed); + } + + if (Constants.STEP_TRIGGER_PRIMITIVE_NODOINVIARPT.equals(triggerPrimitivePair.getSecond())) { + rptStats.addNoCartOnTotal(); + } else { + rptStats.addCartOnTotal(); + } + + String sessionId = triggerPrimitivePair.getFirst(); + List receipts = rtRepository.findStatusInfoBySessionId(sessionId); + + // Fallback if no RT is written in receipts-rt + if (receipts.isEmpty()) { + sessionIdsInError.add(sessionId); + } + + for (RTEntity receipt : receipts) { + setStatisticsForReceipt(receipt, rptStats, triggerPrimitivePair, sessionIdsInError); + } + } + log.info("[Report Generation][Step ] At [{}] preliminary checks on receipts were generated on all triggered primitives!", LocalDateTime.now()); + + // Cataloguing errors by triggered business process + alreadyAnalyzedCount = 0; + int numberOfSessionIdsInError = sessionIdsInError.size(); + Map pairs = new HashMap<>(); + + log.info("[Report Generation][Step ] Analysing the causes for which KO receipts were generated on [{}] sessions...", numberOfSessionIdsInError); + for (String sessionId : sessionIdsInError) { + + // Showing "already analyzed" count for better tracking process + alreadyAnalyzedCount++; + if (alreadyAnalyzedCount % 5000 == 0) { + float percentageAnalyzed = CommonUtility.safeDivide(alreadyAnalyzedCount * 100f, numberOfSessionIdsInError); + log.info("[Report Generation][Step ] At [{}] analysis of causes on which KO receipts were made on [{}/{}] triggered primitives ({}%)...", LocalDateTime.now(), alreadyAnalyzedCount, numberOfSessionIdsInError, percentageAnalyzed); + } + + // Add only one business process for each distinct sessionId + List events = reEventRepository.findRtTriggerRelatedEventBySessionId(sessionId); + events.forEach(event -> pairs.putIfAbsent(event.getSessionId(), event.getBusinessProcess())); + } + + // aggiorna il conteggio delle primitive non concluse con successo per stato di errore + pairs.forEach((sessionId, businessProcess) -> rptStats.addNotCompletedTriggeredPrimitives(businessProcess.replace("-", "_"))); + + reportRepository.save(reportMapper.toEntity(rptStats)); + log.info("[Report Generation][End ] Ended report generation for {}.", day); + } + + public void generateWeeklyReport(String dayOfThisWeek) { + + log.info("[Report Generation][Start] Started weekly report generation for week previous than day {}.", dayOfThisWeek); + String yesterday = CommonUtility.getYesterday(dayOfThisWeek); + mergeMultipleReports(CommonUtility.getWeekInDate(yesterday), ReportType.WEEKLY); + log.info("[Report Generation][End ] Ended monthly report generation for week that includes day {}.", dayOfThisWeek); + } + + public void generateMonthlyReport(String dayOfThisMonth) { + + log.info("[Report Generation][Start] Started monthly report generation for month previous than day {}.", dayOfThisMonth); + String yesterday = CommonUtility.getYesterday(dayOfThisMonth); + mergeMultipleReports(CommonUtility.getMonthInDate(yesterday), ReportType.MONTHLY); + log.info("[Report Generation][End ] Ended monthly report generation for month that includes day {}.", dayOfThisMonth); + } + + public void mergeMultipleReports(List days, ReportType type) { + + ReportEntity mergedReportEntity = new ReportEntity(); + String dateRange = days.get(0) + "_" + days.get(days.size() - 1); + mergedReportEntity.setId(dateRange + "_" + type.name().toLowerCase()); + mergedReportEntity.setDate(dateRange); + + for (String day : days) { + String reportId = String.format("%s_%s", day, ReportType.DAILY.name().toLowerCase()); + Optional entityOpt = reportRepository.findById(reportId, new PartitionKey(day)); + entityOpt.ifPresent(mergedReportEntity::merge); + } + + reportRepository.save(mergedReportEntity); + } +} diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/service/model/report/NotCompletedRPTStatistic.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/service/model/report/NotCompletedRPTStatistic.java new file mode 100644 index 0000000..f6b268a --- /dev/null +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/service/model/report/NotCompletedRPTStatistic.java @@ -0,0 +1,22 @@ +package it.gov.pagopa.wispconverter.technicalsupport.service.model.report; + + +import lombok.Data; + +@Data +public class NotCompletedRPTStatistic { + + private RPTStatisticDetail rejected; + private RPTStatisticDetail notSentEndRetry; + private RPTStatisticDetail sendingOrScheduled; + private RPTStatisticDetail ongoing; + private RPTStatisticDetail neverSent; + + public NotCompletedRPTStatistic() { + this.rejected = new RPTStatisticDetail(); + this.notSentEndRetry = new RPTStatisticDetail(); + this.sendingOrScheduled = new RPTStatisticDetail(); + this.ongoing = new RPTStatisticDetail(); + this.neverSent = new RPTStatisticDetail(); + } +} diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/service/model/report/RPTStatistic.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/service/model/report/RPTStatistic.java new file mode 100644 index 0000000..d54db5b --- /dev/null +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/service/model/report/RPTStatistic.java @@ -0,0 +1,73 @@ +package it.gov.pagopa.wispconverter.technicalsupport.service.model.report; + +import lombok.Data; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +@Data +public class RPTStatistic { + + private String date; + private ReportType type; + + private long totalTriggerPrimitivesOnNdp; + private long totalTriggerPrimitivesOnWisp; + + private long totalRPTCarts; + private long totalRPTSingles; + + private Set completedRPTCarts; + private Set completedRPTSingles; + + private int receiptOkSent; + private int receiptKoSent; + + private Map notCompletedTriggeredPrimitivesByStatus; + private NotCompletedRPTStatistic notCompletedRPTs; + + public RPTStatistic() { + this.totalTriggerPrimitivesOnNdp = 0; + this.totalTriggerPrimitivesOnWisp = 0; + this.totalRPTCarts = 0; + this.totalRPTSingles = 0; + this.receiptOkSent = 0; + this.receiptKoSent = 0; + this.completedRPTCarts = new HashSet<>(); + this.completedRPTSingles = new HashSet<>(); + this.notCompletedTriggeredPrimitivesByStatus = new HashMap<>(); + this.notCompletedRPTs = new NotCompletedRPTStatistic(); + } + + public void addCartOnTotal() { + this.totalRPTCarts++; + } + + public void addNoCartOnTotal() { + this.totalRPTSingles++; + } + + public void addReceiptOkSent() { + this.receiptOkSent++; + } + + public void addReceiptKoSent() { + this.receiptKoSent++; + } + + public void addCompletedRPTCarts(String sessionId) { + this.completedRPTCarts.add(sessionId); + } + + public void addCompletedRPTSingles(String sessionId) { + this.completedRPTSingles.add(sessionId); + } + + public void addNotCompletedTriggeredPrimitives(String status) { + Integer counter = this.notCompletedTriggeredPrimitivesByStatus.computeIfAbsent(status, k -> 0); + counter++; + this.notCompletedTriggeredPrimitivesByStatus.put(status, counter); + } +} diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/service/model/report/RPTStatisticDetail.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/service/model/report/RPTStatisticDetail.java new file mode 100644 index 0000000..03ded4f --- /dev/null +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/service/model/report/RPTStatisticDetail.java @@ -0,0 +1,32 @@ +package it.gov.pagopa.wispconverter.technicalsupport.service.model.report; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.HashSet; +import java.util.Set; + +@AllArgsConstructor +@Data +public class RPTStatisticDetail { + + private int receiptOkCount; + private int receiptKoCount; + private Set receiptsIds; + + public RPTStatisticDetail() { + this.receiptOkCount = 0; + this.receiptKoCount = 0; + this.receiptsIds = new HashSet<>(); + } + + public void addAsOkReceipt(String receiptId) { + this.receiptOkCount++; + this.receiptsIds.add(receiptId); + } + + public void addAsKoReceipt(String receiptId) { + this.receiptKoCount++; + this.receiptsIds.add(receiptId); + } +} diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/service/model/report/ReportType.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/service/model/report/ReportType.java new file mode 100644 index 0000000..504f736 --- /dev/null +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/service/model/report/ReportType.java @@ -0,0 +1,7 @@ +package it.gov.pagopa.wispconverter.technicalsupport.service.model.report; + +public enum ReportType { + DAILY, + WEEKLY, + MONTHLY +} diff --git a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/util/CommonUtility.java b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/util/CommonUtility.java index 68a41aa..588edbb 100644 --- a/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/util/CommonUtility.java +++ b/src/main/java/it/gov/pagopa/wispconverter/technicalsupport/util/CommonUtility.java @@ -5,14 +5,13 @@ import java.io.ByteArrayInputStream; import java.io.IOException; +import java.time.DayOfWeek; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; -import java.util.Base64; -import java.util.Calendar; -import java.util.List; -import java.util.Optional; +import java.time.temporal.TemporalAdjusters; +import java.util.*; import java.util.zip.GZIPInputStream; @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -119,4 +118,51 @@ public static String decompressGZip(String gzipContent) { } return result; } + + public static float safeDivide(float denominator, float numerator) { + float value = 0; + if (denominator != 0) { + value = numerator / denominator; + } + return value; + } + + public static String getYesterday(String date) { + + LocalDate passedDate = LocalDate.parse(date); + return passedDate.minusDays(1).toString(); + } + + public static List getWeekInDate(String date) { + + // Convert the string in format "yyyy-MM-dd" and calculate the first day of the week (monday) + LocalDate passedDate = LocalDate.parse(date); + LocalDate weekStart = passedDate.minusDays((long) passedDate.getDayOfWeek().getValue() - DayOfWeek.MONDAY.getValue()); + + // Extract the days of the week + List weekDates = new ArrayList<>(); + for (int i = 0; i < 7; i++) { + weekDates.add(weekStart.plusDays(i).toString()); + } + + return weekDates; + } + + public static List getMonthInDate(String date) { + + // Convert the input string in format "yyyy-MM-dd" and calculate the first and last days of the month for the provided date + LocalDate passedDate = LocalDate.parse(date); + LocalDate firstDayOfMonth = passedDate.with(TemporalAdjusters.firstDayOfMonth()); + LocalDate lastDayOfMonth = passedDate.with(TemporalAdjusters.lastDayOfMonth()); + + // Collect all the dates of the month + List monthDates = new ArrayList<>(); + LocalDate currentDate = firstDayOfMonth; + while (!currentDate.isAfter(lastDayOfMonth)) { + monthDates.add(currentDate.toString()); + currentDate = currentDate.plusDays(1); + } + + return monthDates; + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 64f9611..3b2bf40 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -39,3 +39,9 @@ azure.cosmos.read.region=${COSMOS_READ_REGION:West Europe} azure.dataexplorer.url=${DATAEXPLORER_URL} azure.dataexplorer.dbName=re + +# Cronjobs configuration +cron.job.schedule.report-generation.enabled=${CRONJOB_REPORTGENERATION_ENABLED:false} +cron.job.schedule.report-generation.daily.cron=${CRONJOB_REPORTGENERATION_DAILY_SCHEDULE:00 00 01 * * *} +cron.job.schedule.report-generation.weekly.cron=${CRONJOB_REPORTGENERATION_WEEKLY_SCHEDULE:00 00 06 * * 1} +cron.job.schedule.report-generation.monthly.cron=${CRONJOB_REPORTGENERATION_MONTHLY_SCHEDULE:00 30 06 1 * *} diff --git a/src/test/java/it/gov/pagopa/wispconverter/technicalsupport/TechnicalSupportControllerTest.java b/src/test/java/it/gov/pagopa/wispconverter/technicalsupport/TechnicalSupportControllerTest.java index 9d1a7b2..ad6fa50 100644 --- a/src/test/java/it/gov/pagopa/wispconverter/technicalsupport/TechnicalSupportControllerTest.java +++ b/src/test/java/it/gov/pagopa/wispconverter/technicalsupport/TechnicalSupportControllerTest.java @@ -3,7 +3,7 @@ import it.gov.pagopa.wispconverter.technicalsupport.controller.model.ReEventResponse; import it.gov.pagopa.wispconverter.technicalsupport.repository.ReEventRepository; -import it.gov.pagopa.wispconverter.technicalsupport.repository.model.ReEventEntity; +import it.gov.pagopa.wispconverter.technicalsupport.repository.model.re.ReEventEntity; import lombok.SneakyThrows; import org.junit.jupiter.api.Test; import org.junit.runner.RunWith;