From 22bc8620b7292e148c5795646e12f8d8c169faa5 Mon Sep 17 00:00:00 2001 From: LarissaASLeite <105739146+LarissaASLeite@users.noreply.github.com> Date: Tue, 21 Jan 2025 11:10:05 +0100 Subject: [PATCH] feat: P4ADEV-1744-add-api-finalize-sync-status (#15) Co-authored-by: RiccardoGiuliani --- build.gradle.kts | 12 +- gradle.lockfile | 2 + openapi/generated.openapi.json | 519 +++++++++--------- openapi/p4pa-debt-position.openapi.yaml | 14 +- .../DebtPositionControllerImpl.java | 20 + .../DebtPositionExceptionHandler.java | 46 ++ .../custom/InvalidStatusException.java | 8 + .../mapper/DebtPositionMapper.java | 25 + .../mapper/InstallmentMapper.java | 31 ++ .../mapper/PaymentOptionMapper.java | 19 + .../pu/debtpositions/mapper/PersonMapper.java | 1 - .../debtpositions/mapper/TransferMapper.java | 19 + .../pu/debtpositions/model/Transfer.java | 2 + .../repository/DebtPositionRepository.java | 18 +- .../InstallmentNoPIIRepository.java | 13 + .../repository/PaymentOptionRepository.java | 12 + .../service/DebtPositionServiceImpl.java | 9 +- ...PositionHierarchyStatusAlignerService.java | 11 + ...tionHierarchyStatusAlignerServiceImpl.java | 78 +++ .../statusalign/StatusRulesHandler.java | 92 ++++ ...DebtPositionInnerStatusAlignerService.java | 8 + ...PositionInnerStatusAlignerServiceImpl.java | 18 + .../DebtPositionStatusChecker.java | 63 +++ ...aymentOptionInnerStatusAlignerService.java | 8 + ...ntOptionInnerStatusAlignerServiceImpl.java | 19 + .../PaymentOptionStatusChecker.java | 64 +++ .../pu/debtpositions/util/Utilities.java | 16 + .../DebtPositionControllerTest.java | 64 +++ .../DebtPositionExceptionHandlerTest.java | 60 ++ .../mapper/DebtPositionMapperTest.java | 51 +- .../mapper/InstallmentMapperTest.java | 52 +- .../mapper/InstallmentPIIMapperTest.java | 5 +- .../mapper/PaymentOptionMapperTest.java | 49 +- .../mapper/PersonMapperTest.java | 18 +- .../mapper/TransferMapperTest.java | 29 +- ...tionHierarchyStatusAlignerServiceTest.java | 126 +++++ ...PositionInnerStatusAlignerServiceTest.java | 61 ++ .../DebtPositionStatusCheckerTest.java | 218 ++++++++ ...ntOptionInnerStatusAlignerServiceTest.java | 62 +++ .../PaymentOptionStatusCheckerTest.java | 217 ++++++++ .../debtpositions/util/ReflectionUtils.java | 40 ++ .../pu/debtpositions/util/TestUtils.java | 102 +++- .../pu/debtpositions/util/UtilitiesTest.java | 30 + .../util/faker/DebtPositionFaker.java | 2 +- .../util/faker/InstallmentFaker.java | 9 +- .../util/faker/PaymentOptionFaker.java | 9 +- 46 files changed, 2032 insertions(+), 319 deletions(-) create mode 100644 src/main/java/it/gov/pagopa/pu/debtpositions/exception/DebtPositionExceptionHandler.java create mode 100644 src/main/java/it/gov/pagopa/pu/debtpositions/exception/custom/InvalidStatusException.java create mode 100644 src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/DebtPositionHierarchyStatusAlignerService.java create mode 100644 src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/DebtPositionHierarchyStatusAlignerServiceImpl.java create mode 100644 src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/StatusRulesHandler.java create mode 100644 src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/debtposition/DebtPositionInnerStatusAlignerService.java create mode 100644 src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/debtposition/DebtPositionInnerStatusAlignerServiceImpl.java create mode 100644 src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/debtposition/DebtPositionStatusChecker.java create mode 100644 src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/paymentoption/PaymentOptionInnerStatusAlignerService.java create mode 100644 src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/paymentoption/PaymentOptionInnerStatusAlignerServiceImpl.java create mode 100644 src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/paymentoption/PaymentOptionStatusChecker.java create mode 100644 src/main/java/it/gov/pagopa/pu/debtpositions/util/Utilities.java create mode 100644 src/test/java/it/gov/pagopa/pu/debtpositions/controller/DebtPositionControllerTest.java create mode 100644 src/test/java/it/gov/pagopa/pu/debtpositions/exception/DebtPositionExceptionHandlerTest.java create mode 100644 src/test/java/it/gov/pagopa/pu/debtpositions/service/statusalign/DebtPositionHierarchyStatusAlignerServiceTest.java create mode 100644 src/test/java/it/gov/pagopa/pu/debtpositions/service/statusalign/debtposition/DebtPositionInnerStatusAlignerServiceTest.java create mode 100644 src/test/java/it/gov/pagopa/pu/debtpositions/service/statusalign/debtposition/DebtPositionStatusCheckerTest.java create mode 100644 src/test/java/it/gov/pagopa/pu/debtpositions/service/statusalign/paymentoption/PaymentOptionInnerStatusAlignerServiceTest.java create mode 100644 src/test/java/it/gov/pagopa/pu/debtpositions/service/statusalign/paymentoption/PaymentOptionStatusCheckerTest.java create mode 100644 src/test/java/it/gov/pagopa/pu/debtpositions/util/ReflectionUtils.java create mode 100644 src/test/java/it/gov/pagopa/pu/debtpositions/util/UtilitiesTest.java diff --git a/build.gradle.kts b/build.gradle.kts index f9bf15ff..a9087ad7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -32,8 +32,8 @@ val springDocOpenApiVersion = "2.7.0" val openApiToolsVersion = "0.2.6" val micrometerVersion = "1.4.1" val postgresJdbcVersion = "42.7.4" - val bouncycastleVersion = "1.79" +val mapStructVersion = "1.6.3" dependencies { implementation("org.springframework.boot:spring-boot-starter") @@ -58,10 +58,20 @@ dependencies { compileOnly("org.projectlombok:lombok") annotationProcessor("org.projectlombok:lombok") + /** + * Mapstruct + * https://mapstruct.org/ + * mapstruct dependencies must always be placed after the lombok dependency + * or the generated mappers will return an empty object + **/ + implementation("org.mapstruct:mapstruct:$mapStructVersion") + annotationProcessor("org.mapstruct:mapstruct-processor:$mapStructVersion") + // Testing testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.mockito:mockito-core") testImplementation ("org.projectlombok:lombok") + testAnnotationProcessor("org.projectlombok:lombok") testImplementation("com.h2database:h2") } diff --git a/gradle.lockfile b/gradle.lockfile index 4f2c81a5..22633b94 100644 --- a/gradle.lockfile +++ b/gradle.lockfile @@ -60,6 +60,8 @@ org.atteo:evo-inflector:1.3=compileClasspath org.bouncycastle:bcprov-jdk18on:1.79=compileClasspath org.hibernate.orm:hibernate-core:6.6.4.Final=compileClasspath org.jspecify:jspecify:1.0.0=compileClasspath +org.mapstruct:mapstruct-processor:1.6.3=compileClasspath +org.mapstruct:mapstruct:1.6.3=compileClasspath org.openapitools:jackson-databind-nullable:0.2.6=compileClasspath org.postgresql:postgresql:42.7.4=compileClasspath org.projectlombok:lombok:1.18.36=compileClasspath diff --git a/openapi/generated.openapi.json b/openapi/generated.openapi.json index c68c31e0..d887a947 100644 --- a/openapi/generated.openapi.json +++ b/openapi/generated.openapi.json @@ -3105,7 +3105,7 @@ "schema" : { "type" : "object", "additionalProperties" : { - "$ref" : "#/components/schemas/IudSyncStatusUpdateDTO" + "$ref" : "#/components/schemas/IupdSyncStatusUpdateDTO" } } } @@ -3496,6 +3496,54 @@ } } }, + "DebtPositionTypeWithCount" : { + "type" : "object", + "properties" : { + "debtPositionTypeId" : { + "type" : "integer", + "format" : "int64" + }, + "code" : { + "type" : "string" + }, + "description" : { + "type" : "string" + }, + "updateDate" : { + "type" : "string", + "format" : "date-time" + }, + "activeOrganizations" : { + "type" : "integer", + "format" : "int32" + }, + "_links" : { + "$ref" : "#/components/schemas/Links" + } + } + }, + "PagedModelDebtPositionTypeWithCount" : { + "type" : "object", + "properties" : { + "_embedded" : { + "type" : "object", + "properties" : { + "debtPositionTypeWithCounts" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/DebtPositionTypeWithCount" + } + } + } + }, + "_links" : { + "$ref" : "#/components/schemas/Links" + }, + "page" : { + "$ref" : "#/components/schemas/PageMetadata" + } + } + }, "DebtPositionTypeOrgOperators" : { "type" : "object", "properties" : { @@ -3548,7 +3596,7 @@ } } }, - "ReceiptNoPII" : { + "DebtPositionType" : { "type" : "object", "properties" : { "creationDate" : { @@ -3562,109 +3610,62 @@ "updateOperatorExternalId" : { "type" : "string" }, - "receiptId" : { - "type" : "integer", - "format" : "int64" - }, - "installmentId" : { + "debtPositionTypeId" : { "type" : "integer", "format" : "int64" }, - "paymentReceiptId" : { + "brokerId" : { "type" : "integer", "format" : "int64" }, - "noticeNumber" : { - "type" : "string" - }, - "orgFiscalCode" : { - "type" : "string" - }, - "outcome" : { - "type" : "string" - }, - "creditorReferenceId" : { + "code" : { "type" : "string" }, - "paymentAmountCents" : { - "type" : "integer", - "format" : "int64" - }, "description" : { "type" : "string" }, - "companyName" : { - "type" : "string" - }, - "officeName" : { - "type" : "string" - }, - "idPsp" : { - "type" : "string" - }, - "pspFiscalCode" : { - "type" : "string" - }, - "pspPartitaIva" : { + "orgType" : { "type" : "string" }, - "pspCompanyName" : { + "macroArea" : { "type" : "string" }, - "idChannel" : { + "serviceType" : { "type" : "string" }, - "channelDescription" : { + "collectingReason" : { "type" : "string" }, - "paymentMethod" : { + "taxonomyCode" : { "type" : "string" }, - "feeCents" : { - "type" : "integer", - "format" : "int64" - }, - "paymentDateTime" : { - "type" : "string", - "format" : "date-time" - }, - "applicationDate" : { - "type" : "string", - "format" : "date-time" + "flagAnonymousFiscalCode" : { + "type" : "boolean" }, - "transferDate" : { - "type" : "string", - "format" : "date-time" + "flagMandatoryDueDate" : { + "type" : "boolean" }, - "standin" : { + "flagNotifyIo" : { "type" : "boolean" }, - "debtorEntityType" : { + "ioTemplateMessage" : { "type" : "string" }, - "personalDataId" : { - "type" : "integer", - "format" : "int64" - }, - "debtorFiscalCodeHash" : { - "type" : "string", - "format" : "byte" - }, "_links" : { "$ref" : "#/components/schemas/Links" } } }, - "PagedModelReceiptNoPII" : { + "PagedModelDebtPositionType" : { "type" : "object", "properties" : { "_embedded" : { "type" : "object", "properties" : { - "receiptNoPIIs" : { + "debtPositionTypes" : { "type" : "array", "items" : { - "$ref" : "#/components/schemas/ReceiptNoPII" + "$ref" : "#/components/schemas/DebtPositionType" } } } @@ -3715,7 +3716,7 @@ }, "status" : { "type" : "string", - "enum" : [ "TO_SYNC", "REPORTED", "PAID", "CANCELLED", "INVALID", "EXPIRED", "UNPAID" ] + "enum" : [ "TO_SYNC", "REPORTED", "PAID", "CANCELLED", "INVALID", "EXPIRED", "UNPAID", "DRAFT" ] }, "iupdPagopa" : { "type" : "string" @@ -3838,7 +3839,7 @@ } } }, - "PaymentOption" : { + "ReceiptNoPII" : { "type" : "object", "properties" : { "creationDate" : { @@ -3852,152 +3853,109 @@ "updateOperatorExternalId" : { "type" : "string" }, - "paymentOptionId" : { + "receiptId" : { "type" : "integer", "format" : "int64" }, - "debtPositionId" : { + "installmentId" : { "type" : "integer", "format" : "int64" }, - "totalAmountCents" : { + "paymentReceiptId" : { "type" : "integer", "format" : "int64" }, - "status" : { - "type" : "string", - "enum" : [ "TO_SYNC", "REPORTED", "PAID", "PARTIALLY_PAID", "CANCELLED", "INVALID", "EXPIRED", "UNPAID" ] - }, - "multiDebtor" : { - "type" : "boolean" - }, - "dueDate" : { - "type" : "string", - "format" : "date-time" + "noticeNumber" : { + "type" : "string" }, - "description" : { + "orgFiscalCode" : { "type" : "string" }, - "paymentOptionType" : { - "type" : "string", - "enum" : [ "SINGLE_INSTALLMENT", "INSTALMENTS", "DOWN_PAYMENT" ] + "outcome" : { + "type" : "string" }, - "_links" : { - "$ref" : "#/components/schemas/Links" - } - } - }, - "PagedModelPaymentOption" : { - "type" : "object", - "properties" : { - "_embedded" : { - "type" : "object", - "properties" : { - "paymentOptions" : { - "type" : "array", - "items" : { - "$ref" : "#/components/schemas/PaymentOption" - } - } - } + "creditorReferenceId" : { + "type" : "string" }, - "_links" : { - "$ref" : "#/components/schemas/Links" + "paymentAmountCents" : { + "type" : "integer", + "format" : "int64" }, - "page" : { - "$ref" : "#/components/schemas/PageMetadata" - } - } - }, - "CollectionModelInstallmentNoPII" : { - "type" : "object", - "properties" : { - "_embedded" : { - "type" : "object", - "properties" : { - "installmentNoPIIs" : { - "type" : "array", - "items" : { - "$ref" : "#/components/schemas/InstallmentNoPIIResponse" - } - } - } + "description" : { + "type" : "string" }, - "_links" : { - "$ref" : "#/components/schemas/Links" - } - } - }, - "DebtPosition" : { - "type" : "object", - "properties" : { - "creationDate" : { - "type" : "string", - "format" : "date-time" + "companyName" : { + "type" : "string" }, - "updateDate" : { - "type" : "string", - "format" : "date-time" + "officeName" : { + "type" : "string" }, - "updateOperatorExternalId" : { + "idPsp" : { "type" : "string" }, - "debtPositionId" : { - "type" : "integer", - "format" : "int64" + "pspFiscalCode" : { + "type" : "string" }, - "iupdOrg" : { + "pspPartitaIva" : { "type" : "string" }, - "description" : { + "pspCompanyName" : { "type" : "string" }, - "status" : { - "type" : "string", - "enum" : [ "TO_SYNC", "REPORTED", "PAID", "PARTIALLY_PAID", "CANCELLED", "INVALID", "EXPIRED", "UNPAID" ] + "idChannel" : { + "type" : "string" }, - "ingestionFlowFileId" : { - "type" : "integer", - "format" : "int64" + "channelDescription" : { + "type" : "string" }, - "ingestionFlowFileLineNumber" : { - "type" : "integer", - "format" : "int64" + "paymentMethod" : { + "type" : "string" }, - "organizationId" : { + "feeCents" : { "type" : "integer", "format" : "int64" }, - "debtPositionTypeOrgId" : { - "type" : "integer", - "format" : "int64" + "paymentDateTime" : { + "type" : "string", + "format" : "date-time" }, - "notificationDate" : { + "applicationDate" : { "type" : "string", "format" : "date-time" }, - "validityDate" : { + "transferDate" : { "type" : "string", "format" : "date-time" }, - "flagIuvVolatile" : { + "standin" : { "type" : "boolean" }, + "debtorEntityType" : { + "type" : "string" + }, + "personalDataId" : { + "type" : "integer", + "format" : "int64" + }, + "debtorFiscalCodeHash" : { + "type" : "string", + "format" : "byte" + }, "_links" : { "$ref" : "#/components/schemas/Links" } } }, - "PagedModelDebtPosition" : { + "PagedModelReceiptNoPII" : { "type" : "object", "properties" : { "_embedded" : { "type" : "object", "properties" : { - "debtPositions" : { + "receiptNoPIIs" : { "type" : "array", "items" : { - "$ref" : "#/components/schemas/DebtPosition" + "$ref" : "#/components/schemas/ReceiptNoPII" } } } @@ -4010,61 +3968,72 @@ } } }, - "CollectionModelPaymentOption" : { + "Transfer" : { "type" : "object", "properties" : { - "_embedded" : { - "type" : "object", - "properties" : { - "paymentOptions" : { - "type" : "array", - "items" : { - "$ref" : "#/components/schemas/PaymentOptionResponse" - } - } - } + "creationDate" : { + "type" : "string", + "format" : "date-time" }, - "_links" : { - "$ref" : "#/components/schemas/Links" - } - } - }, - "DebtPositionTypeWithCount" : { - "type" : "object", - "properties" : { - "debtPositionTypeId" : { + "updateDate" : { + "type" : "string", + "format" : "date-time" + }, + "updateOperatorExternalId" : { + "type" : "string" + }, + "transferId" : { "type" : "integer", "format" : "int64" }, - "code" : { + "installmentId" : { + "type" : "integer", + "format" : "int64" + }, + "orgFiscalCode" : { "type" : "string" }, - "description" : { + "orgName" : { "type" : "string" }, - "updateDate" : { - "type" : "string", - "format" : "date-time" + "amountCents" : { + "type" : "integer", + "format" : "int64" }, - "activeOrganizations" : { + "remittanceInformation" : { + "type" : "string" + }, + "stamp" : { + "$ref" : "#/components/schemas/Stamp" + }, + "iban" : { + "type" : "string" + }, + "postalIban" : { + "type" : "string" + }, + "category" : { + "type" : "string" + }, + "transferIndex" : { "type" : "integer", - "format" : "int32" + "format" : "int64" }, "_links" : { "$ref" : "#/components/schemas/Links" } } }, - "PagedModelDebtPositionTypeWithCount" : { + "PagedModelTransfer" : { "type" : "object", "properties" : { "_embedded" : { "type" : "object", "properties" : { - "debtPositionTypeWithCounts" : { + "transfers" : { "type" : "array", "items" : { - "$ref" : "#/components/schemas/DebtPositionTypeWithCount" + "$ref" : "#/components/schemas/Transfer" } } } @@ -4077,7 +4046,7 @@ } } }, - "DebtPositionType" : { + "PaymentOption" : { "type" : "object", "properties" : { "creationDate" : { @@ -4091,62 +4060,51 @@ "updateOperatorExternalId" : { "type" : "string" }, - "debtPositionTypeId" : { + "paymentOptionId" : { "type" : "integer", "format" : "int64" }, - "brokerId" : { + "debtPositionId" : { "type" : "integer", "format" : "int64" }, - "code" : { - "type" : "string" - }, - "description" : { - "type" : "string" - }, - "orgType" : { - "type" : "string" - }, - "macroArea" : { - "type" : "string" - }, - "serviceType" : { - "type" : "string" - }, - "collectingReason" : { - "type" : "string" - }, - "taxonomyCode" : { - "type" : "string" + "totalAmountCents" : { + "type" : "integer", + "format" : "int64" }, - "flagAnonymousFiscalCode" : { - "type" : "boolean" + "status" : { + "type" : "string", + "enum" : [ "TO_SYNC", "REPORTED", "PAID", "PARTIALLY_PAID", "CANCELLED", "INVALID", "EXPIRED", "UNPAID", "DRAFT" ] }, - "flagMandatoryDueDate" : { + "multiDebtor" : { "type" : "boolean" }, - "flagNotifyIo" : { - "type" : "boolean" + "dueDate" : { + "type" : "string", + "format" : "date-time" }, - "ioTemplateMessage" : { + "description" : { "type" : "string" }, + "paymentOptionType" : { + "type" : "string", + "enum" : [ "SINGLE_INSTALLMENT", "INSTALMENTS", "DOWN_PAYMENT" ] + }, "_links" : { "$ref" : "#/components/schemas/Links" } } }, - "PagedModelDebtPositionType" : { + "PagedModelPaymentOption" : { "type" : "object", "properties" : { "_embedded" : { "type" : "object", "properties" : { - "debtPositionTypes" : { + "paymentOptions" : { "type" : "array", "items" : { - "$ref" : "#/components/schemas/DebtPositionType" + "$ref" : "#/components/schemas/PaymentOption" } } } @@ -4159,7 +4117,26 @@ } } }, - "Transfer" : { + "CollectionModelInstallmentNoPII" : { + "type" : "object", + "properties" : { + "_embedded" : { + "type" : "object", + "properties" : { + "installmentNoPIIs" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/InstallmentNoPIIResponse" + } + } + } + }, + "_links" : { + "$ref" : "#/components/schemas/Links" + } + } + }, + "DebtPosition" : { "type" : "object", "properties" : { "creationDate" : { @@ -4173,58 +4150,62 @@ "updateOperatorExternalId" : { "type" : "string" }, - "transferId" : { - "type" : "integer", - "format" : "int64" - }, - "installmentId" : { + "debtPositionId" : { "type" : "integer", "format" : "int64" }, - "orgFiscalCode" : { + "iupdOrg" : { "type" : "string" }, - "orgName" : { + "description" : { "type" : "string" }, - "amountCents" : { + "status" : { + "type" : "string", + "enum" : [ "TO_SYNC", "REPORTED", "PAID", "PARTIALLY_PAID", "CANCELLED", "INVALID", "EXPIRED", "UNPAID", "DRAFT" ] + }, + "ingestionFlowFileId" : { "type" : "integer", "format" : "int64" }, - "remittanceInformation" : { - "type" : "string" + "ingestionFlowFileLineNumber" : { + "type" : "integer", + "format" : "int64" }, - "stamp" : { - "$ref" : "#/components/schemas/Stamp" + "organizationId" : { + "type" : "integer", + "format" : "int64" }, - "iban" : { - "type" : "string" + "debtPositionTypeOrgId" : { + "type" : "integer", + "format" : "int64" }, - "postalIban" : { - "type" : "string" + "notificationDate" : { + "type" : "string", + "format" : "date-time" }, - "category" : { - "type" : "string" + "validityDate" : { + "type" : "string", + "format" : "date-time" }, - "transferIndex" : { - "type" : "integer", - "format" : "int64" + "flagIuvVolatile" : { + "type" : "boolean" }, "_links" : { "$ref" : "#/components/schemas/Links" } } }, - "PagedModelTransfer" : { + "PagedModelDebtPosition" : { "type" : "object", "properties" : { "_embedded" : { "type" : "object", "properties" : { - "transfers" : { + "debtPositions" : { "type" : "array", "items" : { - "$ref" : "#/components/schemas/Transfer" + "$ref" : "#/components/schemas/DebtPosition" } } } @@ -4237,6 +4218,25 @@ } } }, + "CollectionModelPaymentOption" : { + "type" : "object", + "properties" : { + "_embedded" : { + "type" : "object", + "properties" : { + "paymentOptions" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PaymentOptionResponse" + } + } + } + }, + "_links" : { + "$ref" : "#/components/schemas/Links" + } + } + }, "DebtPositionTypeOrgOperatorsRequestBody" : { "type" : "object", "properties" : { @@ -4467,7 +4467,7 @@ }, "status" : { "type" : "string", - "enum" : [ "TO_SYNC", "REPORTED", "PAID", "PARTIALLY_PAID", "CANCELLED", "INVALID", "EXPIRED", "UNPAID" ] + "enum" : [ "TO_SYNC", "REPORTED", "PAID", "PARTIALLY_PAID", "CANCELLED", "INVALID", "EXPIRED", "UNPAID", "DRAFT" ] }, "ingestionFlowFileId" : { "type" : "integer", @@ -4528,7 +4528,7 @@ }, "status" : { "type" : "string", - "enum" : [ "TO_SYNC", "REPORTED", "PAID", "CANCELLED", "INVALID", "EXPIRED", "UNPAID" ] + "enum" : [ "TO_SYNC", "REPORTED", "PAID", "CANCELLED", "INVALID", "EXPIRED", "UNPAID", "DRAFT" ] }, "iupdPagopa" : { "type" : "string" @@ -4622,7 +4622,7 @@ }, "status" : { "type" : "string", - "enum" : [ "TO_SYNC", "REPORTED", "PAID", "PARTIALLY_PAID", "CANCELLED", "INVALID", "EXPIRED", "UNPAID" ] + "enum" : [ "TO_SYNC", "REPORTED", "PAID", "PARTIALLY_PAID", "CANCELLED", "INVALID", "EXPIRED", "UNPAID", "DRAFT" ] }, "multiDebtor" : { "type" : "boolean" @@ -4723,7 +4723,7 @@ }, "status" : { "type" : "string", - "enum" : [ "TO_SYNC", "REPORTED", "PAID", "CANCELLED", "INVALID", "EXPIRED", "UNPAID" ] + "enum" : [ "TO_SYNC", "REPORTED", "PAID", "CANCELLED", "INVALID", "EXPIRED", "UNPAID", "DRAFT" ] }, "iupdPagopa" : { "type" : "string" @@ -4811,7 +4811,7 @@ }, "status" : { "type" : "string", - "enum" : [ "TO_SYNC", "REPORTED", "PAID", "PARTIALLY_PAID", "CANCELLED", "INVALID", "EXPIRED", "UNPAID" ] + "enum" : [ "TO_SYNC", "REPORTED", "PAID", "PARTIALLY_PAID", "CANCELLED", "INVALID", "EXPIRED", "UNPAID", "DRAFT" ] }, "multiDebtor" : { "type" : "boolean" @@ -5002,7 +5002,7 @@ }, "status" : { "type" : "string", - "enum" : [ "TO_SYNC", "REPORTED", "PAID", "PARTIALLY_PAID", "CANCELLED", "INVALID", "EXPIRED", "UNPAID" ] + "enum" : [ "TO_SYNC", "REPORTED", "PAID", "PARTIALLY_PAID", "CANCELLED", "INVALID", "EXPIRED", "UNPAID", "DRAFT" ] }, "ingestionFlowFileId" : { "type" : "integer", @@ -5061,7 +5061,7 @@ }, "status" : { "type" : "string", - "enum" : [ "TO_SYNC", "REPORTED", "PAID", "CANCELLED", "INVALID", "EXPIRED", "UNPAID" ] + "enum" : [ "TO_SYNC", "REPORTED", "PAID", "CANCELLED", "INVALID", "EXPIRED", "UNPAID", "DRAFT" ] }, "iupdPagopa" : { "type" : "string" @@ -5145,7 +5145,7 @@ }, "status" : { "type" : "string", - "enum" : [ "TO_SYNC", "REPORTED", "PAID", "PARTIALLY_PAID", "CANCELLED", "INVALID", "EXPIRED", "UNPAID" ] + "enum" : [ "TO_SYNC", "REPORTED", "PAID", "PARTIALLY_PAID", "CANCELLED", "INVALID", "EXPIRED", "UNPAID", "DRAFT" ] }, "multiDebtor" : { "type" : "boolean" @@ -5267,11 +5267,12 @@ } } }, - "IudSyncStatusUpdateDTO" : { + "IupdSyncStatusUpdateDTO" : { "type" : "object", "properties" : { "newStatus" : { - "type" : "string" + "type" : "string", + "enum" : [ "TO_SYNC", "REPORTED", "PAID", "CANCELLED", "INVALID", "EXPIRED", "UNPAID", "DRAFT" ] }, "iupdPagopa" : { "type" : "string" diff --git a/openapi/p4pa-debt-position.openapi.yaml b/openapi/p4pa-debt-position.openapi.yaml index 51e14ca5..a3dc8b26 100644 --- a/openapi/p4pa-debt-position.openapi.yaml +++ b/openapi/p4pa-debt-position.openapi.yaml @@ -77,7 +77,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/SyncStatusDTO" + $ref: "#/components/schemas/SyncStatusUpdateRequestDTO" required: true responses: "200": @@ -393,6 +393,7 @@ components: - INVALID - EXPIRED - UNPAID + - DRAFT PaymentOptionStatus: type: string enum: @@ -404,6 +405,7 @@ components: - INVALID - EXPIRED - UNPAID + - DRAFT InstallmentStatus: type: string enum: @@ -414,17 +416,19 @@ components: - INVALID - EXPIRED - UNPAID - SyncStatusDTO: + - DRAFT + SyncStatusUpdateRequestDTO: type: object additionalProperties: - $ref: "#/components/schemas/IudSyncStatusUpdateDTO" - IudSyncStatusUpdateDTO: + $ref: "#/components/schemas/IupdSyncStatusUpdateDTO" + IupdSyncStatusUpdateDTO: type: object properties: newStatus: - type: string + $ref: "#/components/schemas/InstallmentStatus" iupdPagopa: type: string + DebtPositionErrorDTO: type: object required: diff --git a/src/main/java/it/gov/pagopa/pu/debtpositions/controller/DebtPositionControllerImpl.java b/src/main/java/it/gov/pagopa/pu/debtpositions/controller/DebtPositionControllerImpl.java index 854f7ee3..cc94a696 100644 --- a/src/main/java/it/gov/pagopa/pu/debtpositions/controller/DebtPositionControllerImpl.java +++ b/src/main/java/it/gov/pagopa/pu/debtpositions/controller/DebtPositionControllerImpl.java @@ -2,13 +2,33 @@ import it.gov.pagopa.pu.debtpositions.controller.generated.DebtPositionApi; import it.gov.pagopa.pu.debtpositions.dto.generated.DebtPositionDTO; +import it.gov.pagopa.pu.debtpositions.dto.generated.IupdSyncStatusUpdateDTO; +import it.gov.pagopa.pu.debtpositions.service.statusalign.DebtPositionHierarchyStatusAlignerService; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RestController; +import java.util.Map; + @RestController public class DebtPositionControllerImpl implements DebtPositionApi { + + private final DebtPositionHierarchyStatusAlignerService debtPositionHierarchyStatusAlignerService; + + public DebtPositionControllerImpl(DebtPositionHierarchyStatusAlignerService debtPositionHierarchyStatusAlignerService) { + this.debtPositionHierarchyStatusAlignerService = debtPositionHierarchyStatusAlignerService; + } + @Override public ResponseEntity createDebtPosition(DebtPositionDTO debtPositionDTO, Boolean massive) { return DebtPositionApi.super.createDebtPosition(debtPositionDTO, massive); } + + + @Override + public ResponseEntity finalizeSyncStatus(Long debtPositionId, Map requestBody) { + DebtPositionDTO body = debtPositionHierarchyStatusAlignerService.finalizeSyncStatus(debtPositionId, requestBody); + return new ResponseEntity<>(body, HttpStatus.OK); + } } + diff --git a/src/main/java/it/gov/pagopa/pu/debtpositions/exception/DebtPositionExceptionHandler.java b/src/main/java/it/gov/pagopa/pu/debtpositions/exception/DebtPositionExceptionHandler.java new file mode 100644 index 00000000..d38c2d2d --- /dev/null +++ b/src/main/java/it/gov/pagopa/pu/debtpositions/exception/DebtPositionExceptionHandler.java @@ -0,0 +1,46 @@ +package it.gov.pagopa.pu.debtpositions.exception; + +import it.gov.pagopa.pu.debtpositions.dto.generated.DebtPositionErrorDTO; +import it.gov.pagopa.pu.debtpositions.exception.custom.InvalidStatusException; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + + +@RestControllerAdvice +@Slf4j +@Order(Ordered.HIGHEST_PRECEDENCE) +public class DebtPositionExceptionHandler { + + @ExceptionHandler({InvalidStatusException.class}) + public ResponseEntity handleInternalError(RuntimeException ex, HttpServletRequest request){ + return handleWorkflowErrorException(ex, request, HttpStatus.BAD_REQUEST, DebtPositionErrorDTO.CodeEnum.IVALID_REQUEST); + } + + static ResponseEntity handleWorkflowErrorException(RuntimeException ex, HttpServletRequest request, HttpStatus httpStatus, DebtPositionErrorDTO.CodeEnum errorEnum) { + String message = logException(ex, request, httpStatus); + + return ResponseEntity + .status(httpStatus) + .body(DebtPositionErrorDTO.builder().code(errorEnum).message(message).build()); + } + + private static String logException(RuntimeException ex, HttpServletRequest request, HttpStatus httpStatus) { + String message = ex.getMessage(); + log.info("A {} occurred handling request {}: HttpStatus {} - {}", + ex.getClass(), + getRequestDetails(request), + httpStatus.value(), + message); + return message; + } + + static String getRequestDetails(HttpServletRequest request) { + return "%s %s".formatted(request.getMethod(), request.getRequestURI()); + } +} diff --git a/src/main/java/it/gov/pagopa/pu/debtpositions/exception/custom/InvalidStatusException.java b/src/main/java/it/gov/pagopa/pu/debtpositions/exception/custom/InvalidStatusException.java new file mode 100644 index 00000000..6cb026f4 --- /dev/null +++ b/src/main/java/it/gov/pagopa/pu/debtpositions/exception/custom/InvalidStatusException.java @@ -0,0 +1,8 @@ +package it.gov.pagopa.pu.debtpositions.exception.custom; + +public class InvalidStatusException extends RuntimeException{ + + public InvalidStatusException(String message) { + super(message); + } +} diff --git a/src/main/java/it/gov/pagopa/pu/debtpositions/mapper/DebtPositionMapper.java b/src/main/java/it/gov/pagopa/pu/debtpositions/mapper/DebtPositionMapper.java index eaffb3b1..807defd2 100644 --- a/src/main/java/it/gov/pagopa/pu/debtpositions/mapper/DebtPositionMapper.java +++ b/src/main/java/it/gov/pagopa/pu/debtpositions/mapper/DebtPositionMapper.java @@ -12,6 +12,8 @@ import java.util.stream.Collector; import java.util.stream.Collectors; +import static it.gov.pagopa.pu.debtpositions.util.Utilities.localDatetimeToOffsetDateTime; + @Service public class DebtPositionMapper { @@ -53,4 +55,27 @@ public Pair> mapToModel(DebtPos return Pair.of(debtPosition, installmentMapping); } + + public DebtPositionDTO mapToDto(DebtPosition debtPosition){ + return DebtPositionDTO.builder() + .debtPositionId(debtPosition.getDebtPositionId()) + .iupdOrg(debtPosition.getIupdOrg()) + .description(debtPosition.getDescription()) + .status(debtPosition.getStatus()) + .ingestionFlowFileId(debtPosition.getIngestionFlowFileId()) + .ingestionFlowFileLineNumber(debtPosition.getIngestionFlowFileLineNumber()) + .organizationId(debtPosition.getOrganizationId()) + .debtPositionTypeOrgId(debtPosition.getDebtPositionTypeOrgId()) + .notificationDate(debtPosition.getNotificationDate()) + .validityDate(debtPosition.getValidityDate()) + .flagIuvVolatile(debtPosition.isFlagIuvVolatile()) + .creationDate(localDatetimeToOffsetDateTime(debtPosition.getCreationDate())) + .updateDate(localDatetimeToOffsetDateTime(debtPosition.getUpdateDate())) + .paymentOptions( + debtPosition.getPaymentOptions().stream() + .map(paymentOptionMapper::mapToDto) + .toList() + ) .build(); + } } + diff --git a/src/main/java/it/gov/pagopa/pu/debtpositions/mapper/InstallmentMapper.java b/src/main/java/it/gov/pagopa/pu/debtpositions/mapper/InstallmentMapper.java index 74c86716..2b746450 100644 --- a/src/main/java/it/gov/pagopa/pu/debtpositions/mapper/InstallmentMapper.java +++ b/src/main/java/it/gov/pagopa/pu/debtpositions/mapper/InstallmentMapper.java @@ -2,8 +2,11 @@ import it.gov.pagopa.pu.debtpositions.dto.Installment; import it.gov.pagopa.pu.debtpositions.dto.generated.InstallmentDTO; +import it.gov.pagopa.pu.debtpositions.model.InstallmentNoPII; import org.springframework.stereotype.Service; +import static it.gov.pagopa.pu.debtpositions.util.Utilities.localDatetimeToOffsetDateTime; + @Service public class InstallmentMapper { @@ -44,4 +47,32 @@ public Installment mapToModel(InstallmentDTO dto) { return installment; } + public InstallmentDTO mapToDto(InstallmentNoPII installment) { + return InstallmentDTO.builder() + .installmentId(installment.getInstallmentId()) + .paymentOptionId(installment.getPaymentOptionId()) + .status(installment.getStatus()) + .iupdPagopa(installment.getIupdPagopa()) + .iud(installment.getIud()) + .iuv(installment.getIuv()) + .iur(installment.getIur()) + .iuf(installment.getIuf()) + .nav(installment.getNav()) + .dueDate(installment.getDueDate()) + .paymentTypeCode(installment.getPaymentTypeCode()) + .amountCents(installment.getAmountCents()) + .notificationFeeCents(installment.getNotificationFeeCents()) + .remittanceInformation(installment.getRemittanceInformation()) + .humanFriendlyRemittanceInformation(installment.getHumanFriendlyRemittanceInformation()) + .balance(installment.getBalance()) + .legacyPaymentMetadata(installment.getLegacyPaymentMetadata()) + .transfers(installment.getTransfers().stream() + .map(transferMapper::mapToDto) + .toList()) + .creationDate(localDatetimeToOffsetDateTime(installment.getCreationDate())) + .updateDate(localDatetimeToOffsetDateTime(installment.getUpdateDate())) + .build(); + } + + } diff --git a/src/main/java/it/gov/pagopa/pu/debtpositions/mapper/PaymentOptionMapper.java b/src/main/java/it/gov/pagopa/pu/debtpositions/mapper/PaymentOptionMapper.java index 24e22711..8258efba 100644 --- a/src/main/java/it/gov/pagopa/pu/debtpositions/mapper/PaymentOptionMapper.java +++ b/src/main/java/it/gov/pagopa/pu/debtpositions/mapper/PaymentOptionMapper.java @@ -55,4 +55,23 @@ public Pair> mapToModel(Paymen return Pair.of(paymentOption, installmentMapping); } + + public PaymentOptionDTO mapToDto(PaymentOption paymentOption) { + return PaymentOptionDTO.builder() + .paymentOptionId(paymentOption.getPaymentOptionId()) + .debtPositionId(paymentOption.getDebtPositionId()) + .totalAmountCents(paymentOption.getTotalAmountCents()) + .status(paymentOption.getStatus()) + .multiDebtor(paymentOption.isMultiDebtor()) + .dueDate(paymentOption.getDueDate()) + .description(paymentOption.getDescription()) + .paymentOptionType(PaymentOptionDTO.PaymentOptionTypeEnum.valueOf(paymentOption.getPaymentOptionType().name())) + .installments( + paymentOption.getInstallments().stream() + .map(installmentMapper::mapToDto) + .toList() + ) + .build(); + } + } diff --git a/src/main/java/it/gov/pagopa/pu/debtpositions/mapper/PersonMapper.java b/src/main/java/it/gov/pagopa/pu/debtpositions/mapper/PersonMapper.java index d44fe51f..ec4adbb1 100644 --- a/src/main/java/it/gov/pagopa/pu/debtpositions/mapper/PersonMapper.java +++ b/src/main/java/it/gov/pagopa/pu/debtpositions/mapper/PersonMapper.java @@ -21,5 +21,4 @@ public Person mapToModel(PersonDTO dto) { person.setEmail(dto.getEmail()); return person; } - } diff --git a/src/main/java/it/gov/pagopa/pu/debtpositions/mapper/TransferMapper.java b/src/main/java/it/gov/pagopa/pu/debtpositions/mapper/TransferMapper.java index 08437ac5..e6528f58 100644 --- a/src/main/java/it/gov/pagopa/pu/debtpositions/mapper/TransferMapper.java +++ b/src/main/java/it/gov/pagopa/pu/debtpositions/mapper/TransferMapper.java @@ -24,4 +24,23 @@ public Transfer mapToModel(TransferDTO dto) { return transfer; } + public TransferDTO mapToDto(Transfer transfer) { + return TransferDTO.builder() + .transferId(transfer.getTransferId()) + .installmentId(transfer.getInstallmentId()) + .orgFiscalCode(transfer.getOrgFiscalCode()) + .orgName(transfer.getOrgName()) + .amountCents(transfer.getAmountCents()) + .remittanceInformation(transfer.getRemittanceInformation()) + .stampType(transfer.getStamp().getStampType()) + .stampHashDocument(transfer.getStamp().getStampHashDocument()) + .stampProvincialResidence(transfer.getStamp().getStampProvincialResidence()) + .iban(transfer.getIban()) + .postalIban(transfer.getPostalIban()) + .category(transfer.getCategory()) + .transferIndex(transfer.getTransferIndex()) + .build(); + } + + } diff --git a/src/main/java/it/gov/pagopa/pu/debtpositions/model/Transfer.java b/src/main/java/it/gov/pagopa/pu/debtpositions/model/Transfer.java index cd58dcf7..fec5c6ef 100644 --- a/src/main/java/it/gov/pagopa/pu/debtpositions/model/Transfer.java +++ b/src/main/java/it/gov/pagopa/pu/debtpositions/model/Transfer.java @@ -3,6 +3,7 @@ import jakarta.annotation.Nonnull; import jakarta.persistence.*; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; @@ -15,6 +16,7 @@ @AllArgsConstructor @NoArgsConstructor @Data +@Builder @EqualsAndHashCode(of = "transferId", callSuper = false) public class Transfer extends BaseEntity implements Serializable, Comparable { diff --git a/src/main/java/it/gov/pagopa/pu/debtpositions/repository/DebtPositionRepository.java b/src/main/java/it/gov/pagopa/pu/debtpositions/repository/DebtPositionRepository.java index 786892f3..4911a04f 100644 --- a/src/main/java/it/gov/pagopa/pu/debtpositions/repository/DebtPositionRepository.java +++ b/src/main/java/it/gov/pagopa/pu/debtpositions/repository/DebtPositionRepository.java @@ -1,13 +1,25 @@ package it.gov.pagopa.pu.debtpositions.repository; +import it.gov.pagopa.pu.debtpositions.dto.generated.DebtPositionStatus; import it.gov.pagopa.pu.debtpositions.model.DebtPosition; +import jakarta.transaction.Transactional; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.data.rest.core.annotation.RepositoryRestResource; +import org.springframework.data.rest.core.annotation.RestResource; @RepositoryRestResource(path = "debt-positions") -public interface DebtPositionRepository extends JpaRepository { +public interface DebtPositionRepository extends JpaRepository { - @EntityGraph(value = "completeDebtPosition") - DebtPosition findOneWithAllDataByDebtPositionId(Long debtPositionId); + @EntityGraph(value = "completeDebtPosition") + DebtPosition findOneWithAllDataByDebtPositionId(Long debtPositionId); + + @RestResource(exported = false) + @Transactional + @Modifying + @Query("UPDATE DebtPosition d SET d.status = :status WHERE d.debtPositionId = :debtPositionId") + void updateStatus(@Param("debtPositionId") Long debtPositionId, @Param("status") DebtPositionStatus status); } diff --git a/src/main/java/it/gov/pagopa/pu/debtpositions/repository/InstallmentNoPIIRepository.java b/src/main/java/it/gov/pagopa/pu/debtpositions/repository/InstallmentNoPIIRepository.java index a30da6cf..7ad0ac9d 100644 --- a/src/main/java/it/gov/pagopa/pu/debtpositions/repository/InstallmentNoPIIRepository.java +++ b/src/main/java/it/gov/pagopa/pu/debtpositions/repository/InstallmentNoPIIRepository.java @@ -1,10 +1,23 @@ package it.gov.pagopa.pu.debtpositions.repository; +import it.gov.pagopa.pu.debtpositions.dto.generated.InstallmentStatus; import it.gov.pagopa.pu.debtpositions.model.InstallmentNoPII; +import jakarta.transaction.Transactional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.data.rest.core.annotation.RepositoryRestResource; +import org.springframework.data.rest.core.annotation.RestResource; @RepositoryRestResource(path = "installments") public interface InstallmentNoPIIRepository extends JpaRepository { + @RestResource(exported = false) + @Transactional + @Modifying + @Query("UPDATE InstallmentNoPII i SET i.status = :status, i.iupdPagopa = :iupdPagopa WHERE i.installmentId = :installmentId") + void updateStatusAndIupdPagopa(@Param("installmentId") Long installmentId, @Param("iupdPagopa") String iupdPagopa, @Param("status") InstallmentStatus status); + + } diff --git a/src/main/java/it/gov/pagopa/pu/debtpositions/repository/PaymentOptionRepository.java b/src/main/java/it/gov/pagopa/pu/debtpositions/repository/PaymentOptionRepository.java index d2613bb2..195100f0 100644 --- a/src/main/java/it/gov/pagopa/pu/debtpositions/repository/PaymentOptionRepository.java +++ b/src/main/java/it/gov/pagopa/pu/debtpositions/repository/PaymentOptionRepository.java @@ -1,10 +1,22 @@ package it.gov.pagopa.pu.debtpositions.repository; +import it.gov.pagopa.pu.debtpositions.dto.generated.PaymentOptionStatus; import it.gov.pagopa.pu.debtpositions.model.PaymentOption; +import jakarta.transaction.Transactional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.data.rest.core.annotation.RepositoryRestResource; +import org.springframework.data.rest.core.annotation.RestResource; @RepositoryRestResource(path = "payment-options") public interface PaymentOptionRepository extends JpaRepository { + @RestResource(exported = false) + @Transactional + @Modifying + @Query("UPDATE PaymentOption p SET p.status = :status WHERE p.paymentOptionId = :paymentOptionId") + void updateStatus(@Param("paymentOptionId") Long paymentOptionId, @Param("status") PaymentOptionStatus status); + } diff --git a/src/main/java/it/gov/pagopa/pu/debtpositions/service/DebtPositionServiceImpl.java b/src/main/java/it/gov/pagopa/pu/debtpositions/service/DebtPositionServiceImpl.java index 09b8d713..f9900e65 100644 --- a/src/main/java/it/gov/pagopa/pu/debtpositions/service/DebtPositionServiceImpl.java +++ b/src/main/java/it/gov/pagopa/pu/debtpositions/service/DebtPositionServiceImpl.java @@ -2,16 +2,21 @@ import it.gov.pagopa.pu.debtpositions.dto.Installment; import it.gov.pagopa.pu.debtpositions.dto.generated.DebtPositionDTO; -import it.gov.pagopa.pu.debtpositions.mapper.*; +import it.gov.pagopa.pu.debtpositions.mapper.DebtPositionMapper; import it.gov.pagopa.pu.debtpositions.model.DebtPosition; import it.gov.pagopa.pu.debtpositions.model.InstallmentNoPII; import it.gov.pagopa.pu.debtpositions.model.PaymentOption; -import it.gov.pagopa.pu.debtpositions.repository.*; +import it.gov.pagopa.pu.debtpositions.repository.DebtPositionRepository; +import it.gov.pagopa.pu.debtpositions.repository.InstallmentPIIRepository; +import it.gov.pagopa.pu.debtpositions.repository.PaymentOptionRepository; +import it.gov.pagopa.pu.debtpositions.repository.TransferRepository; import jakarta.transaction.Transactional; import org.springframework.data.util.Pair; +import org.springframework.stereotype.Service; import java.util.Map; +@Service public class DebtPositionServiceImpl implements DebtPositionService { private final DebtPositionRepository debtPositionRepository; diff --git a/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/DebtPositionHierarchyStatusAlignerService.java b/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/DebtPositionHierarchyStatusAlignerService.java new file mode 100644 index 00000000..23c37c65 --- /dev/null +++ b/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/DebtPositionHierarchyStatusAlignerService.java @@ -0,0 +1,11 @@ +package it.gov.pagopa.pu.debtpositions.service.statusalign; + +import it.gov.pagopa.pu.debtpositions.dto.generated.DebtPositionDTO; +import it.gov.pagopa.pu.debtpositions.dto.generated.IupdSyncStatusUpdateDTO; + +import java.util.Map; + +public interface DebtPositionHierarchyStatusAlignerService { + + DebtPositionDTO finalizeSyncStatus(Long debtPositionId, Map syncStatusDTO); +} diff --git a/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/DebtPositionHierarchyStatusAlignerServiceImpl.java b/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/DebtPositionHierarchyStatusAlignerServiceImpl.java new file mode 100644 index 00000000..614e2257 --- /dev/null +++ b/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/DebtPositionHierarchyStatusAlignerServiceImpl.java @@ -0,0 +1,78 @@ +package it.gov.pagopa.pu.debtpositions.service.statusalign; + +import it.gov.pagopa.pu.debtpositions.dto.generated.DebtPositionDTO; +import it.gov.pagopa.pu.debtpositions.dto.generated.InstallmentStatus; +import it.gov.pagopa.pu.debtpositions.dto.generated.IupdSyncStatusUpdateDTO; +import it.gov.pagopa.pu.debtpositions.mapper.DebtPositionMapper; +import it.gov.pagopa.pu.debtpositions.model.DebtPosition; +import it.gov.pagopa.pu.debtpositions.repository.DebtPositionRepository; +import it.gov.pagopa.pu.debtpositions.repository.InstallmentNoPIIRepository; +import it.gov.pagopa.pu.debtpositions.service.statusalign.debtposition.DebtPositionInnerStatusAlignerService; +import it.gov.pagopa.pu.debtpositions.service.statusalign.paymentoption.PaymentOptionInnerStatusAlignerService; +import jakarta.transaction.Transactional; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Map; + +import static it.gov.pagopa.pu.debtpositions.dto.generated.InstallmentStatus.TO_SYNC; + +@Service +@Slf4j +public class DebtPositionHierarchyStatusAlignerServiceImpl implements DebtPositionHierarchyStatusAlignerService { + + private final DebtPositionRepository debtPositionRepository; + private final InstallmentNoPIIRepository installmentNoPIIRepository; + private final PaymentOptionInnerStatusAlignerService paymentOptionInnerStatusAlignerService; + private final DebtPositionInnerStatusAlignerService debtPositionInnerStatusAlignerService; + private final DebtPositionMapper debtPositionMapper; + + + public DebtPositionHierarchyStatusAlignerServiceImpl(DebtPositionRepository debtPositionRepository, + InstallmentNoPIIRepository installmentNoPIIRepository, PaymentOptionInnerStatusAlignerService paymentOptionInnerStatusAlignerService, DebtPositionInnerStatusAlignerService debtPositionInnerStatusAlignerService, DebtPositionMapper debtPositionMapper) { + this.debtPositionRepository = debtPositionRepository; + this.installmentNoPIIRepository = installmentNoPIIRepository; + this.paymentOptionInnerStatusAlignerService = paymentOptionInnerStatusAlignerService; + this.debtPositionInnerStatusAlignerService = debtPositionInnerStatusAlignerService; + this.debtPositionMapper = debtPositionMapper; + } + + @Transactional + @Override + public DebtPositionDTO finalizeSyncStatus(Long debtPositionId, Map syncStatusDTO) { + DebtPosition debtPosition = debtPositionRepository.findOneWithAllDataByDebtPositionId(debtPositionId); + + debtPosition.getPaymentOptions().forEach(paymentOption -> + paymentOption.getInstallments().stream() + .filter(installment -> { + boolean isToSync = TO_SYNC.equals(installment.getStatus()); + boolean iud2Update = syncStatusDTO.containsKey(installment.getIud()); + + if (!iud2Update && isToSync) { + log.error("Installment with IUD [{}] is TO_SYNC but not present in the input map", installment.getIud()); + } else if (iud2Update && !isToSync) { + log.error("Installment with IUD [{}] is present in the input map but does not have TO_SYNC status", installment.getIud()); + } + + return isToSync && iud2Update ; + }) + .forEach(installment -> { + IupdSyncStatusUpdateDTO updateDTO = syncStatusDTO.get(installment.getIud()); + + InstallmentStatus newStatus = updateDTO.getNewStatus(); + installment.setStatus(newStatus); + log.info("Updating status {} and iupdPagopa {} for installment with id {}", newStatus, updateDTO.getIupdPagopa(), installment.getInstallmentId()); + installmentNoPIIRepository.updateStatusAndIupdPagopa( + installment.getInstallmentId(), + updateDTO.getIupdPagopa(), + newStatus + ); + }) + ); + + debtPosition.getPaymentOptions().forEach(paymentOptionInnerStatusAlignerService::updatePaymentOptionStatus); + debtPositionInnerStatusAlignerService.updateDebtPositionStatus(debtPosition); + + return debtPositionMapper.mapToDto(debtPosition); + } +} diff --git a/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/StatusRulesHandler.java b/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/StatusRulesHandler.java new file mode 100644 index 00000000..a00598fe --- /dev/null +++ b/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/StatusRulesHandler.java @@ -0,0 +1,92 @@ +package it.gov.pagopa.pu.debtpositions.service.statusalign; + +import lombok.extern.slf4j.Slf4j; + +import java.util.List; +import java.util.Set; + +@Slf4j +public abstract class StatusRulesHandler, T, D> { + + private final E syncStatus; + private final E paidStatus; + private final E unpaidStatus; + private final E expiredStatus; + private final E cancelledStatus; + private final E reportedStatus; + private final E invalidStatus; + + private final Set allowedCancelledStatuses; + private final Set emptyAllowedStatuses; + + protected StatusRulesHandler(E syncStatus, E paidStatus, E unpaidStatus, E expiredStatus, E cancelledStatus, E reportedStatus, E invalidStatus) { + this.syncStatus = syncStatus; + this.paidStatus = paidStatus; + this.unpaidStatus = unpaidStatus; + this.expiredStatus = expiredStatus; + this.cancelledStatus = cancelledStatus; + this.reportedStatus = reportedStatus; + this.invalidStatus = invalidStatus; + + this.allowedCancelledStatuses = Set.of(cancelledStatus); + this.emptyAllowedStatuses = Set.of(); + } + + public void updateEntityStatus(T entity) { + List childStatuses = getChildStatuses(entity); + log.info("Calculating new Status for {}", entity.getClass()); + D newStatus = calculateNewStatus(childStatuses); + setStatus(entity, newStatus); + log.info("Updating entity {} with status {}", entity.getClass(), newStatus); + storeStatus(entity, newStatus); + } + + protected abstract List getChildStatuses(T entity); + + protected abstract D calculateNewStatus(List childStatuses); + + protected abstract void setStatus(T entity, D newStatus); + + protected abstract void storeStatus(T entity, D newStatus); + + public boolean isToSync(List childrenStatusList) { + return childrenStatusList.contains(syncStatus); + } + + public boolean isPartiallyPaid(List childrenStatusList) { + return childrenStatusList.contains(paidStatus) && + (childrenStatusList.contains(unpaidStatus) || childrenStatusList.contains(expiredStatus)); + } + + public boolean isUnpaid(List childrenStatusList) { + return allMatch(childrenStatusList, unpaidStatus, allowedCancelledStatuses); + } + + public boolean isPaid(List childrenStatusList) { + return allMatch(childrenStatusList, paidStatus, allowedCancelledStatuses); + } + + public boolean isReported(List childrenStatusList) { + return allMatch(childrenStatusList, reportedStatus, allowedCancelledStatuses); + } + + public boolean isInvalid(List childrenStatusList) { + return allMatch(childrenStatusList, invalidStatus, allowedCancelledStatuses); + } + + public boolean isCancelled(List childrenStatusList) { + return allMatch(childrenStatusList, cancelledStatus, emptyAllowedStatuses); + } + + public boolean isExpired(List childrenStatusList) { + return allMatch(childrenStatusList, expiredStatus, emptyAllowedStatuses); + } + + private boolean allMatch(List statusList, E requiredState, Set allowedStatuses) { + if (statusList.isEmpty()) { + return false; + } + return statusList.contains(requiredState) && + statusList.stream().allMatch(status -> requiredState.equals(status) || allowedStatuses.contains(status)); + } +} diff --git a/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/debtposition/DebtPositionInnerStatusAlignerService.java b/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/debtposition/DebtPositionInnerStatusAlignerService.java new file mode 100644 index 00000000..fc896e04 --- /dev/null +++ b/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/debtposition/DebtPositionInnerStatusAlignerService.java @@ -0,0 +1,8 @@ +package it.gov.pagopa.pu.debtpositions.service.statusalign.debtposition; + +import it.gov.pagopa.pu.debtpositions.model.DebtPosition; + +public interface DebtPositionInnerStatusAlignerService { + + void updateDebtPositionStatus(DebtPosition debtPosition); +} diff --git a/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/debtposition/DebtPositionInnerStatusAlignerServiceImpl.java b/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/debtposition/DebtPositionInnerStatusAlignerServiceImpl.java new file mode 100644 index 00000000..09408ab5 --- /dev/null +++ b/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/debtposition/DebtPositionInnerStatusAlignerServiceImpl.java @@ -0,0 +1,18 @@ +package it.gov.pagopa.pu.debtpositions.service.statusalign.debtposition; + +import it.gov.pagopa.pu.debtpositions.model.DebtPosition; +import it.gov.pagopa.pu.debtpositions.repository.DebtPositionRepository; +import org.springframework.stereotype.Service; + +@Service +public class DebtPositionInnerStatusAlignerServiceImpl extends DebtPositionStatusChecker implements DebtPositionInnerStatusAlignerService { + + public DebtPositionInnerStatusAlignerServiceImpl(DebtPositionRepository debtPositionRepository) { + super(debtPositionRepository); + } + + @Override + public void updateDebtPositionStatus(DebtPosition debtPosition) { + updateEntityStatus(debtPosition); + } +} diff --git a/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/debtposition/DebtPositionStatusChecker.java b/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/debtposition/DebtPositionStatusChecker.java new file mode 100644 index 00000000..57275541 --- /dev/null +++ b/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/debtposition/DebtPositionStatusChecker.java @@ -0,0 +1,63 @@ +package it.gov.pagopa.pu.debtpositions.service.statusalign.debtposition; + +import it.gov.pagopa.pu.debtpositions.dto.generated.DebtPositionStatus; +import it.gov.pagopa.pu.debtpositions.dto.generated.PaymentOptionStatus; +import it.gov.pagopa.pu.debtpositions.exception.custom.InvalidStatusException; +import it.gov.pagopa.pu.debtpositions.model.DebtPosition; +import it.gov.pagopa.pu.debtpositions.model.PaymentOption; +import it.gov.pagopa.pu.debtpositions.repository.DebtPositionRepository; +import it.gov.pagopa.pu.debtpositions.service.statusalign.StatusRulesHandler; + +import java.util.List; + +import static it.gov.pagopa.pu.debtpositions.dto.generated.PaymentOptionStatus.*; + +public class DebtPositionStatusChecker extends StatusRulesHandler { + + private final DebtPositionRepository debtPositionRepository; + + public DebtPositionStatusChecker(DebtPositionRepository debtPositionRepository) { + super(TO_SYNC, PAID, UNPAID, EXPIRED, CANCELLED, REPORTED, INVALID); + this.debtPositionRepository = debtPositionRepository; + } + + @Override + public DebtPositionStatus calculateNewStatus(List paymentOptionStatusList) { + if (isToSync(paymentOptionStatusList)){ + return DebtPositionStatus.TO_SYNC; + } else if (isPartiallyPaid(paymentOptionStatusList)){ + return DebtPositionStatus.PARTIALLY_PAID; + } else if (isUnpaid(paymentOptionStatusList)){ + return DebtPositionStatus.UNPAID; + } else if (isPaid(paymentOptionStatusList)){ + return DebtPositionStatus.PAID; + } else if (isReported(paymentOptionStatusList)){ + return DebtPositionStatus.REPORTED; + } else if (isInvalid(paymentOptionStatusList)){ + return DebtPositionStatus.INVALID; + } else if (isCancelled(paymentOptionStatusList)){ + return DebtPositionStatus.CANCELLED; + } else if (isExpired(paymentOptionStatusList)){ + return DebtPositionStatus.EXPIRED; + } else { + throw new InvalidStatusException("Unable to determine status for DebtPosition"); + } + } + + @Override + protected List getChildStatuses(DebtPosition debtPosition) { + return debtPosition.getPaymentOptions().stream() + .map(PaymentOption::getStatus) + .toList(); + } + + @Override + protected void setStatus(DebtPosition debtPosition, DebtPositionStatus newStatus) { + debtPosition.setStatus(newStatus); + } + + @Override + protected void storeStatus(DebtPosition debtPosition, DebtPositionStatus newStatus) { + debtPositionRepository.updateStatus(debtPosition.getDebtPositionId(), newStatus); + } +} diff --git a/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/paymentoption/PaymentOptionInnerStatusAlignerService.java b/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/paymentoption/PaymentOptionInnerStatusAlignerService.java new file mode 100644 index 00000000..0b54fb14 --- /dev/null +++ b/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/paymentoption/PaymentOptionInnerStatusAlignerService.java @@ -0,0 +1,8 @@ +package it.gov.pagopa.pu.debtpositions.service.statusalign.paymentoption; + +import it.gov.pagopa.pu.debtpositions.model.PaymentOption; + +public interface PaymentOptionInnerStatusAlignerService { + + void updatePaymentOptionStatus(PaymentOption paymentOption); +} diff --git a/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/paymentoption/PaymentOptionInnerStatusAlignerServiceImpl.java b/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/paymentoption/PaymentOptionInnerStatusAlignerServiceImpl.java new file mode 100644 index 00000000..8dd70204 --- /dev/null +++ b/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/paymentoption/PaymentOptionInnerStatusAlignerServiceImpl.java @@ -0,0 +1,19 @@ +package it.gov.pagopa.pu.debtpositions.service.statusalign.paymentoption; + +import it.gov.pagopa.pu.debtpositions.model.PaymentOption; +import it.gov.pagopa.pu.debtpositions.repository.PaymentOptionRepository; +import org.springframework.stereotype.Service; + +@Service +public class PaymentOptionInnerStatusAlignerServiceImpl extends PaymentOptionStatusChecker implements PaymentOptionInnerStatusAlignerService{ + + + public PaymentOptionInnerStatusAlignerServiceImpl(PaymentOptionRepository paymentOptionRepository) { + super(paymentOptionRepository); + } + + @Override + public void updatePaymentOptionStatus(PaymentOption paymentOption) { + updateEntityStatus(paymentOption); + } +} diff --git a/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/paymentoption/PaymentOptionStatusChecker.java b/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/paymentoption/PaymentOptionStatusChecker.java new file mode 100644 index 00000000..c40adcf6 --- /dev/null +++ b/src/main/java/it/gov/pagopa/pu/debtpositions/service/statusalign/paymentoption/PaymentOptionStatusChecker.java @@ -0,0 +1,64 @@ +package it.gov.pagopa.pu.debtpositions.service.statusalign.paymentoption; + +import it.gov.pagopa.pu.debtpositions.dto.generated.InstallmentStatus; +import it.gov.pagopa.pu.debtpositions.dto.generated.PaymentOptionStatus; +import it.gov.pagopa.pu.debtpositions.exception.custom.InvalidStatusException; +import it.gov.pagopa.pu.debtpositions.model.InstallmentNoPII; +import it.gov.pagopa.pu.debtpositions.model.PaymentOption; +import it.gov.pagopa.pu.debtpositions.repository.PaymentOptionRepository; +import it.gov.pagopa.pu.debtpositions.service.statusalign.StatusRulesHandler; + +import java.util.List; + +import static it.gov.pagopa.pu.debtpositions.dto.generated.PaymentOptionStatus.TO_SYNC; + +public class PaymentOptionStatusChecker extends StatusRulesHandler { + + private final PaymentOptionRepository paymentOptionRepository; + + public PaymentOptionStatusChecker(PaymentOptionRepository paymentOptionRepository) { + super(InstallmentStatus.TO_SYNC, InstallmentStatus.PAID, InstallmentStatus.UNPAID, + InstallmentStatus.EXPIRED, InstallmentStatus.CANCELLED, InstallmentStatus.REPORTED, InstallmentStatus.INVALID); + this.paymentOptionRepository = paymentOptionRepository; + } + + @Override + public PaymentOptionStatus calculateNewStatus(List installmentStatusList) { + if (isToSync(installmentStatusList)) { + return TO_SYNC; + } else if (isPartiallyPaid(installmentStatusList)) { + return PaymentOptionStatus.PARTIALLY_PAID; + } else if (isUnpaid(installmentStatusList)) { + return PaymentOptionStatus.UNPAID; + } else if (isPaid(installmentStatusList)) { + return PaymentOptionStatus.PAID; + } else if (isReported(installmentStatusList)) { + return PaymentOptionStatus.REPORTED; + } else if (isInvalid(installmentStatusList)) { + return PaymentOptionStatus.INVALID; + } else if (isCancelled(installmentStatusList)) { + return PaymentOptionStatus.CANCELLED; + } else if (isExpired(installmentStatusList)) { + return PaymentOptionStatus.EXPIRED; + } else { + throw new InvalidStatusException("Unable to determine status for PaymentOption"); + } + } + + @Override + protected List getChildStatuses(PaymentOption paymentOption) { + return paymentOption.getInstallments().stream() + .map(InstallmentNoPII::getStatus) + .toList(); + } + + @Override + protected void setStatus(PaymentOption paymentOption, PaymentOptionStatus newStatus) { + paymentOption.setStatus(newStatus); + } + + @Override + protected void storeStatus(PaymentOption paymentOption, PaymentOptionStatus newStatus) { + paymentOptionRepository.updateStatus(paymentOption.getPaymentOptionId(), newStatus); + } +} diff --git a/src/main/java/it/gov/pagopa/pu/debtpositions/util/Utilities.java b/src/main/java/it/gov/pagopa/pu/debtpositions/util/Utilities.java new file mode 100644 index 00000000..15c9f518 --- /dev/null +++ b/src/main/java/it/gov/pagopa/pu/debtpositions/util/Utilities.java @@ -0,0 +1,16 @@ +package it.gov.pagopa.pu.debtpositions.util; + +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; + +public class Utilities { + + private Utilities() {} + + public static OffsetDateTime localDatetimeToOffsetDateTime(LocalDateTime localDateTime){ + return localDateTime != null + ? localDateTime.atOffset(ZoneOffset.UTC) + : null; + } +} diff --git a/src/test/java/it/gov/pagopa/pu/debtpositions/controller/DebtPositionControllerTest.java b/src/test/java/it/gov/pagopa/pu/debtpositions/controller/DebtPositionControllerTest.java new file mode 100644 index 00000000..98fc8fda --- /dev/null +++ b/src/test/java/it/gov/pagopa/pu/debtpositions/controller/DebtPositionControllerTest.java @@ -0,0 +1,64 @@ +package it.gov.pagopa.pu.debtpositions.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import it.gov.pagopa.pu.debtpositions.dto.generated.DebtPositionDTO; +import it.gov.pagopa.pu.debtpositions.dto.generated.InstallmentStatus; +import it.gov.pagopa.pu.debtpositions.dto.generated.IupdSyncStatusUpdateDTO; +import it.gov.pagopa.pu.debtpositions.service.statusalign.DebtPositionHierarchyStatusAlignerService; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; + +import java.util.HashMap; +import java.util.Map; + +import static it.gov.pagopa.pu.debtpositions.util.faker.DebtPositionFaker.buildDebtPositionDTO; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(DebtPositionControllerImpl.class) +@AutoConfigureMockMvc(addFilters = false) +class DebtPositionControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockitoBean + private DebtPositionHierarchyStatusAlignerService service; + + @Test + void whenFinalizeSyncStatusThenOk() throws Exception { + Long id = 1L; + InstallmentStatus newStatus = InstallmentStatus.TO_SYNC; + + Map syncStatusDTO = new HashMap<>(); + IupdSyncStatusUpdateDTO iupdSyncStatusUpdateDTO = IupdSyncStatusUpdateDTO.builder() + .newStatus(newStatus) + .iupdPagopa("iupdPagoPa") + .build(); + + syncStatusDTO.put("iud", iupdSyncStatusUpdateDTO); + + Mockito.when(service.finalizeSyncStatus(id, syncStatusDTO)).thenReturn(buildDebtPositionDTO()); + + MvcResult result = mockMvc.perform( + put("/debt-positions/1/finalize-sync-status") + .contentType(MediaType.APPLICATION_JSON_VALUE) + .content(objectMapper.writeValueAsString(syncStatusDTO))) + .andExpect(status().isOk()) + .andReturn(); + + DebtPositionDTO resultResponse = objectMapper.readValue(result.getResponse().getContentAsString(), DebtPositionDTO.class); + assertEquals(buildDebtPositionDTO(), resultResponse); + } +} diff --git a/src/test/java/it/gov/pagopa/pu/debtpositions/exception/DebtPositionExceptionHandlerTest.java b/src/test/java/it/gov/pagopa/pu/debtpositions/exception/DebtPositionExceptionHandlerTest.java new file mode 100644 index 00000000..b5805ba8 --- /dev/null +++ b/src/test/java/it/gov/pagopa/pu/debtpositions/exception/DebtPositionExceptionHandlerTest.java @@ -0,0 +1,60 @@ +package it.gov.pagopa.pu.debtpositions.exception; + +import it.gov.pagopa.pu.debtpositions.exception.custom.InvalidStatusException; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import static org.mockito.Mockito.doThrow; + +@ExtendWith({SpringExtension.class}) +@WebMvcTest(value = {DebtPositionExceptionHandlerTest.TestController.class}, excludeAutoConfiguration = SecurityAutoConfiguration.class) +@ContextConfiguration(classes = { + DebtPositionExceptionHandlerTest.TestController.class, + DebtPositionExceptionHandler.class}) +public class DebtPositionExceptionHandlerTest { + + public static final String DATA = "data"; + @Autowired + private MockMvc mockMvc; + + @MockitoSpyBean + private TestController testControllerSpy; + + @RestController + @Slf4j + static class TestController { + + @GetMapping("/test") + String testEndpoint(@RequestParam(DATA) String data) { + return "OK"; + } + } + + @Test + void handleInvalidStatusExceptionError() throws Exception { + doThrow(new InvalidStatusException("Error")).when(testControllerSpy).testEndpoint(DATA); + + mockMvc.perform(MockMvcRequestBuilders.get("/test") + .param(DATA, DATA) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(MockMvcResultMatchers.status().isBadRequest()) + .andExpect(MockMvcResultMatchers.jsonPath("$.code").value("DEBT_POSITION_IVALID_REQUEST")) + .andExpect(MockMvcResultMatchers.jsonPath("$.message").value("Error")); + + } +} diff --git a/src/test/java/it/gov/pagopa/pu/debtpositions/mapper/DebtPositionMapperTest.java b/src/test/java/it/gov/pagopa/pu/debtpositions/mapper/DebtPositionMapperTest.java index 3f069b74..ef306809 100644 --- a/src/test/java/it/gov/pagopa/pu/debtpositions/mapper/DebtPositionMapperTest.java +++ b/src/test/java/it/gov/pagopa/pu/debtpositions/mapper/DebtPositionMapperTest.java @@ -1,47 +1,74 @@ package it.gov.pagopa.pu.debtpositions.mapper; -import it.gov.pagopa.pu.debtpositions.citizen.service.DataCipherService; import it.gov.pagopa.pu.debtpositions.dto.Installment; import it.gov.pagopa.pu.debtpositions.dto.generated.DebtPositionDTO; +import it.gov.pagopa.pu.debtpositions.dto.generated.DebtPositionStatus; import it.gov.pagopa.pu.debtpositions.model.DebtPosition; import it.gov.pagopa.pu.debtpositions.model.InstallmentNoPII; +import it.gov.pagopa.pu.debtpositions.model.PaymentOption; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.data.util.Pair; +import java.util.HashMap; import java.util.Map; import static it.gov.pagopa.pu.debtpositions.util.TestUtils.checkNotNullFields; -import static it.gov.pagopa.pu.debtpositions.util.faker.DebtPositionFaker.*; -import static org.junit.jupiter.api.Assertions.*; +import static it.gov.pagopa.pu.debtpositions.util.TestUtils.reflectionEqualsByName; +import static it.gov.pagopa.pu.debtpositions.util.faker.DebtPositionFaker.buildDebtPosition; +import static it.gov.pagopa.pu.debtpositions.util.faker.DebtPositionFaker.buildDebtPositionDTO; +import static it.gov.pagopa.pu.debtpositions.util.faker.InstallmentFaker.buildInstallment; +import static it.gov.pagopa.pu.debtpositions.util.faker.InstallmentFaker.buildInstallmentNoPII; +import static it.gov.pagopa.pu.debtpositions.util.faker.PaymentOptionFaker.buildPaymentOption; +import static it.gov.pagopa.pu.debtpositions.util.faker.PaymentOptionFaker.buildPaymentOptionDTO; +@ExtendWith(MockitoExtension.class) class DebtPositionMapperTest { @Mock - private DataCipherService dataCipherServiceMock; + private PaymentOptionMapper paymentOptionMapperMock; private DebtPositionMapper debtPositionMapper; @BeforeEach - void init(){ - PersonMapper personMapper = new PersonMapper(); - TransferMapper transferMapper = new TransferMapper(); - InstallmentMapper installmentMapper = new InstallmentMapper(personMapper, transferMapper); - InstallmentPIIMapper installmentPIIMapper = new InstallmentPIIMapper(dataCipherServiceMock); - PaymentOptionMapper paymentOptionMapper = new PaymentOptionMapper(installmentMapper, installmentPIIMapper); - debtPositionMapper = new DebtPositionMapper(paymentOptionMapper); + void setUp(){ + debtPositionMapper = new DebtPositionMapper(paymentOptionMapperMock); } @Test void givenValidDebtPositionDTO_whenMapToModel_thenReturnDebtPositionAndInstallmentMap() { DebtPosition debtPositionExpected = buildDebtPosition(); + debtPositionExpected.setStatus(DebtPositionStatus.UNPAID); DebtPositionDTO debtPositionDTO = buildDebtPositionDTO(); + Map installmentMap = new HashMap<>(); + installmentMap.put(buildInstallmentNoPII(), buildInstallment()); + + PaymentOption paymentOption = buildPaymentOption(); + Pair> paymentOptionPair = Pair.of(paymentOption, installmentMap); + + Mockito.when(paymentOptionMapperMock.mapToModel(buildPaymentOptionDTO())).thenReturn(paymentOptionPair); + Pair> result = debtPositionMapper.mapToModel(debtPositionDTO); - assertEquals(debtPositionExpected, result.getFirst()); + reflectionEqualsByName(debtPositionExpected, result.getFirst()); checkNotNullFields(result.getFirst(), "updateOperatorExternalId"); } + @Test + void givenMapToDtoThenOk(){ + DebtPositionDTO debtPositionExpected = buildDebtPositionDTO(); + debtPositionExpected.setStatus(DebtPositionStatus.TO_SYNC); + + Mockito.when(paymentOptionMapperMock.mapToDto(buildPaymentOption())).thenReturn(buildPaymentOptionDTO()); + + DebtPositionDTO result = debtPositionMapper.mapToDto(buildDebtPosition()); + + checkNotNullFields(result); + reflectionEqualsByName(debtPositionExpected, result); + } } diff --git a/src/test/java/it/gov/pagopa/pu/debtpositions/mapper/InstallmentMapperTest.java b/src/test/java/it/gov/pagopa/pu/debtpositions/mapper/InstallmentMapperTest.java index 1baf93d6..2ed2f479 100644 --- a/src/test/java/it/gov/pagopa/pu/debtpositions/mapper/InstallmentMapperTest.java +++ b/src/test/java/it/gov/pagopa/pu/debtpositions/mapper/InstallmentMapperTest.java @@ -2,27 +2,69 @@ import it.gov.pagopa.pu.debtpositions.dto.Installment; import it.gov.pagopa.pu.debtpositions.dto.generated.InstallmentDTO; +import it.gov.pagopa.pu.debtpositions.dto.generated.InstallmentStatus; +import it.gov.pagopa.pu.debtpositions.model.InstallmentNoPII; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; +import java.util.TreeSet; import static it.gov.pagopa.pu.debtpositions.util.TestUtils.checkNotNullFields; -import static org.junit.jupiter.api.Assertions.*; +import static it.gov.pagopa.pu.debtpositions.util.TestUtils.reflectionEqualsByName; import static it.gov.pagopa.pu.debtpositions.util.faker.InstallmentFaker.*; +import static it.gov.pagopa.pu.debtpositions.util.faker.PersonFaker.buildPerson; +import static it.gov.pagopa.pu.debtpositions.util.faker.PersonFaker.buildPersonDTO; +import static it.gov.pagopa.pu.debtpositions.util.faker.TransferFaker.buildTransfer; +import static it.gov.pagopa.pu.debtpositions.util.faker.TransferFaker.buildTransferDTO; +@ExtendWith(MockitoExtension.class) class InstallmentMapperTest { - private final PersonMapper personMapper = new PersonMapper(); - private final TransferMapper transferMapper = new TransferMapper(); - private final InstallmentMapper installmentMapper = new InstallmentMapper(personMapper, transferMapper); + @Mock + private PersonMapper personMapperMock; + @Mock + private TransferMapper transferMapperMock; + + private InstallmentMapper installmentMapper; + + @BeforeEach + void setUp(){ + installmentMapper = new InstallmentMapper(personMapperMock, transferMapperMock); + } @Test void givenValidInstallmentDTO_WhenMapToModel_ThenReturnInstallment() { Installment installmentExpected = buildInstallmentNoUpdate(); + installmentExpected.setStatus(InstallmentStatus.UNPAID); InstallmentDTO installmentDTO = buildInstallmentDTO(); + Mockito.when(personMapperMock.mapToModel(buildPersonDTO())).thenReturn(buildPerson()); + Mockito.when(transferMapperMock.mapToModel(buildTransferDTO())).thenReturn(buildTransfer()); + Installment result = installmentMapper.mapToModel(installmentDTO); - assertEquals(installmentExpected, result); + reflectionEqualsByName(installmentExpected, result); checkNotNullFields(result, "updateOperatorExternalId", "noPII"); } + + @Test + void givenMapToDtoThenOk(){ + InstallmentDTO installmentExpected = buildInstallmentDTO(); + installmentExpected.setStatus(InstallmentStatus.TO_SYNC); + InstallmentNoPII installmentNoPII = buildInstallmentNoPII(); + installmentNoPII.setTransfers(new TreeSet<>(List.of(buildTransfer()))); + + Mockito.when(transferMapperMock.mapToDto(buildTransfer())).thenReturn(buildTransferDTO()); + + InstallmentDTO result = installmentMapper.mapToDto(installmentNoPII); + + reflectionEqualsByName(installmentExpected, result, "debtor"); + checkNotNullFields(result, "debtor"); + } } diff --git a/src/test/java/it/gov/pagopa/pu/debtpositions/mapper/InstallmentPIIMapperTest.java b/src/test/java/it/gov/pagopa/pu/debtpositions/mapper/InstallmentPIIMapperTest.java index 88966eb9..c5d1ea95 100644 --- a/src/test/java/it/gov/pagopa/pu/debtpositions/mapper/InstallmentPIIMapperTest.java +++ b/src/test/java/it/gov/pagopa/pu/debtpositions/mapper/InstallmentPIIMapperTest.java @@ -14,6 +14,7 @@ import org.springframework.data.util.Pair; import static it.gov.pagopa.pu.debtpositions.util.TestUtils.checkNotNullFields; +import static it.gov.pagopa.pu.debtpositions.util.TestUtils.reflectionEqualsByName; import static it.gov.pagopa.pu.debtpositions.util.faker.InstallmentFaker.*; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -46,8 +47,8 @@ void testMap(){ Pair result = mapper.map(installment); - assertEquals(installmentNoPIIExpected, result.getFirst()); - assertEquals(installmentPIIDTOExpected, result.getSecond()); + reflectionEqualsByName(installmentNoPIIExpected, result.getFirst()); + reflectionEqualsByName(installmentPIIDTOExpected, result.getSecond()); checkNotNullFields(result.getFirst(), "transfers", "personalDataId", "debtorFiscalCodeHash"); checkNotNullFields(result.getSecond()); } diff --git a/src/test/java/it/gov/pagopa/pu/debtpositions/mapper/PaymentOptionMapperTest.java b/src/test/java/it/gov/pagopa/pu/debtpositions/mapper/PaymentOptionMapperTest.java index 1b2ad19b..9afa00f3 100644 --- a/src/test/java/it/gov/pagopa/pu/debtpositions/mapper/PaymentOptionMapperTest.java +++ b/src/test/java/it/gov/pagopa/pu/debtpositions/mapper/PaymentOptionMapperTest.java @@ -1,39 +1,68 @@ package it.gov.pagopa.pu.debtpositions.mapper; -import it.gov.pagopa.pu.debtpositions.citizen.service.DataCipherService; import it.gov.pagopa.pu.debtpositions.dto.Installment; import it.gov.pagopa.pu.debtpositions.dto.generated.PaymentOptionDTO; +import it.gov.pagopa.pu.debtpositions.dto.generated.PaymentOptionStatus; import it.gov.pagopa.pu.debtpositions.model.InstallmentNoPII; import it.gov.pagopa.pu.debtpositions.model.PaymentOption; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.data.util.Pair; import java.util.Map; import static it.gov.pagopa.pu.debtpositions.util.TestUtils.checkNotNullFields; -import static it.gov.pagopa.pu.debtpositions.util.faker.PaymentOptionFaker.*; -import static org.junit.jupiter.api.Assertions.*; +import static it.gov.pagopa.pu.debtpositions.util.TestUtils.reflectionEqualsByName; +import static it.gov.pagopa.pu.debtpositions.util.faker.InstallmentFaker.*; +import static it.gov.pagopa.pu.debtpositions.util.faker.PaymentOptionFaker.buildPaymentOption; +import static it.gov.pagopa.pu.debtpositions.util.faker.PaymentOptionFaker.buildPaymentOptionDTO; +@ExtendWith(MockitoExtension.class) class PaymentOptionMapperTest { @Mock - private DataCipherService dataCipherServiceMock; + private InstallmentMapper installmentMapperMock; + @Mock + private InstallmentPIIMapper installmentPIIMapperMock; + + private PaymentOptionMapper paymentOptionMapper; - private final PersonMapper personMapper = new PersonMapper(); - private final TransferMapper transferMapper = new TransferMapper(); - private final InstallmentMapper installmentMapper = new InstallmentMapper(personMapper, transferMapper); - private final InstallmentPIIMapper installmentPIIMapper = new InstallmentPIIMapper(dataCipherServiceMock); - private final PaymentOptionMapper paymentOptionMapper = new PaymentOptionMapper(installmentMapper, installmentPIIMapper); + @BeforeEach + void setUp(){ + paymentOptionMapper = new PaymentOptionMapper(installmentMapperMock, installmentPIIMapperMock); + } @Test void givenValidPaymentOptionDTO_WhenMapToModel_ThenReturnPaymentOptionAndInstallmentMap() { PaymentOption paymentOptionExpected = buildPaymentOption(); + paymentOptionExpected.setStatus(PaymentOptionStatus.UNPAID); PaymentOptionDTO paymentOptionDTO = buildPaymentOptionDTO(); + Mockito.when(installmentMapperMock.mapToModel(buildInstallmentDTO())).thenReturn(buildInstallment()); + + Mockito.when(installmentPIIMapperMock.map(buildInstallment())).thenReturn(Pair.of(buildInstallmentNoPII(), buildInstallmentPIIDTO())); + Pair> result = paymentOptionMapper.mapToModel(paymentOptionDTO); - assertEquals(paymentOptionExpected, result.getFirst()); + reflectionEqualsByName(paymentOptionExpected, result.getFirst()); checkNotNullFields(result.getFirst(), "updateOperatorExternalId", "creationDate", "updateDate"); } + + @Test + void givenMapToDtoThenOk(){ + PaymentOptionDTO paymentOptionExpected = buildPaymentOptionDTO(); + paymentOptionExpected.setStatus(PaymentOptionStatus.TO_SYNC); + + Mockito.when(installmentMapperMock.mapToDto(buildInstallmentNoPII())).thenReturn(buildInstallmentDTO()); + + PaymentOptionDTO result = paymentOptionMapper.mapToDto(buildPaymentOption()); + System.out.println("result: "+result); + + reflectionEqualsByName(paymentOptionExpected, result); + checkNotNullFields(result); + } } diff --git a/src/test/java/it/gov/pagopa/pu/debtpositions/mapper/PersonMapperTest.java b/src/test/java/it/gov/pagopa/pu/debtpositions/mapper/PersonMapperTest.java index 2135b07e..bd157b8e 100644 --- a/src/test/java/it/gov/pagopa/pu/debtpositions/mapper/PersonMapperTest.java +++ b/src/test/java/it/gov/pagopa/pu/debtpositions/mapper/PersonMapperTest.java @@ -2,15 +2,25 @@ import it.gov.pagopa.pu.debtpositions.dto.Person; import it.gov.pagopa.pu.debtpositions.dto.generated.PersonDTO; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; import static it.gov.pagopa.pu.debtpositions.util.TestUtils.checkNotNullFields; -import static it.gov.pagopa.pu.debtpositions.util.faker.PersonFaker.*; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static it.gov.pagopa.pu.debtpositions.util.TestUtils.reflectionEqualsByName; +import static it.gov.pagopa.pu.debtpositions.util.faker.PersonFaker.buildPerson; +import static it.gov.pagopa.pu.debtpositions.util.faker.PersonFaker.buildPersonDTO; +@ExtendWith(MockitoExtension.class) class PersonMapperTest { - private final PersonMapper personMapper = new PersonMapper(); + private PersonMapper personMapper; + + @BeforeEach + void setUp() { + personMapper = new PersonMapper(); + } @Test void givenValidPersonDTO_WhenMapToModel_ThenReturnPerson() { @@ -19,7 +29,7 @@ void givenValidPersonDTO_WhenMapToModel_ThenReturnPerson() { Person result = personMapper.mapToModel(personDTO); - assertEquals(personExpected, result); + reflectionEqualsByName(personExpected, result); checkNotNullFields(result); } } diff --git a/src/test/java/it/gov/pagopa/pu/debtpositions/mapper/TransferMapperTest.java b/src/test/java/it/gov/pagopa/pu/debtpositions/mapper/TransferMapperTest.java index 835a033a..444f4619 100644 --- a/src/test/java/it/gov/pagopa/pu/debtpositions/mapper/TransferMapperTest.java +++ b/src/test/java/it/gov/pagopa/pu/debtpositions/mapper/TransferMapperTest.java @@ -2,15 +2,25 @@ import it.gov.pagopa.pu.debtpositions.dto.generated.TransferDTO; import it.gov.pagopa.pu.debtpositions.model.Transfer; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; import static it.gov.pagopa.pu.debtpositions.util.TestUtils.checkNotNullFields; -import static it.gov.pagopa.pu.debtpositions.util.faker.TransferFaker.*; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static it.gov.pagopa.pu.debtpositions.util.TestUtils.reflectionEqualsByName; +import static it.gov.pagopa.pu.debtpositions.util.faker.TransferFaker.buildTransfer; +import static it.gov.pagopa.pu.debtpositions.util.faker.TransferFaker.buildTransferDTO; +@ExtendWith(MockitoExtension.class) class TransferMapperTest { - private final TransferMapper transferMapper = new TransferMapper(); + private TransferMapper transferMapper; + + @BeforeEach + void setUp(){ + transferMapper = new TransferMapper(); + } @Test void givenValidTransferDTO_WhenMapToModel_ThenReturnTransfer() { @@ -19,7 +29,18 @@ void givenValidTransferDTO_WhenMapToModel_ThenReturnTransfer() { Transfer result = transferMapper.mapToModel(transferDTO); - assertEquals(transferExpected, result); + reflectionEqualsByName(transferExpected, result); checkNotNullFields(result, "updateOperatorExternalId", "creationDate", "updateDate"); } + + @Test + void givenMapToDtoThenOk(){ + TransferDTO transferExpected = buildTransferDTO(); + + TransferDTO result = transferMapper.mapToDto(buildTransfer()); + + reflectionEqualsByName(transferExpected, result); + checkNotNullFields(result, "updateOperatorExternalId", "creationDate", "updateDate"); + + } } diff --git a/src/test/java/it/gov/pagopa/pu/debtpositions/service/statusalign/DebtPositionHierarchyStatusAlignerServiceTest.java b/src/test/java/it/gov/pagopa/pu/debtpositions/service/statusalign/DebtPositionHierarchyStatusAlignerServiceTest.java new file mode 100644 index 00000000..5bfed30c --- /dev/null +++ b/src/test/java/it/gov/pagopa/pu/debtpositions/service/statusalign/DebtPositionHierarchyStatusAlignerServiceTest.java @@ -0,0 +1,126 @@ +package it.gov.pagopa.pu.debtpositions.service.statusalign; + +import it.gov.pagopa.pu.debtpositions.dto.generated.*; +import it.gov.pagopa.pu.debtpositions.mapper.DebtPositionMapper; +import it.gov.pagopa.pu.debtpositions.model.DebtPosition; +import it.gov.pagopa.pu.debtpositions.repository.DebtPositionRepository; +import it.gov.pagopa.pu.debtpositions.repository.InstallmentNoPIIRepository; +import it.gov.pagopa.pu.debtpositions.service.statusalign.debtposition.DebtPositionInnerStatusAlignerService; +import it.gov.pagopa.pu.debtpositions.service.statusalign.paymentoption.PaymentOptionInnerStatusAlignerService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.HashMap; +import java.util.Map; + +import static it.gov.pagopa.pu.debtpositions.util.TestUtils.reflectionEqualsByName; +import static it.gov.pagopa.pu.debtpositions.util.faker.DebtPositionFaker.buildDebtPosition; +import static it.gov.pagopa.pu.debtpositions.util.faker.DebtPositionFaker.buildDebtPositionDTO; +import static it.gov.pagopa.pu.debtpositions.util.faker.PaymentOptionFaker.buildPaymentOption; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class DebtPositionHierarchyStatusAlignerServiceTest { + + @Mock + private DebtPositionRepository debtPositionRepositoryMock; + @Mock + private InstallmentNoPIIRepository installmentNoPIIRepositoryMock; + @Mock + private PaymentOptionInnerStatusAlignerService paymentOptionInnerStatusAlignerServiceMock; + @Mock + private DebtPositionInnerStatusAlignerService debtPositionInnerStatusAlignerServiceMock; + @Mock + private DebtPositionMapper debtPositionMapperMock; + + private DebtPositionHierarchyStatusAlignerService service; + + @BeforeEach + void setUp() { + service = new DebtPositionHierarchyStatusAlignerServiceImpl(debtPositionRepositoryMock, installmentNoPIIRepositoryMock, paymentOptionInnerStatusAlignerServiceMock, debtPositionInnerStatusAlignerServiceMock, debtPositionMapperMock); + } + + @Test + void givenFinalizeSyncStatusThenOk() { + Long id = 1L; + InstallmentStatus newStatus = InstallmentStatus.UNPAID; + String iupdPagoPa = "iupdPagoPa"; + DebtPosition debtPosition = buildDebtPosition(); + + Mockito.when(debtPositionRepositoryMock.findOneWithAllDataByDebtPositionId(id)).thenReturn(debtPosition); + Mockito.doNothing().when(installmentNoPIIRepositoryMock).updateStatusAndIupdPagopa(id, iupdPagoPa, newStatus); + Mockito.doNothing().when(paymentOptionInnerStatusAlignerServiceMock).updatePaymentOptionStatus(buildPaymentOption()); + Mockito.doNothing().when(debtPositionInnerStatusAlignerServiceMock).updateDebtPositionStatus(debtPosition); + Mockito.when(debtPositionMapperMock.mapToDto(debtPosition)).thenReturn(buildDebtPositionDTO()); + + Map syncStatusDTO = new HashMap<>(); + IupdSyncStatusUpdateDTO iupdSyncStatusUpdateDTO = IupdSyncStatusUpdateDTO.builder() + .newStatus(newStatus) + .iupdPagopa(iupdPagoPa) + .build(); + + syncStatusDTO.put("iud", iupdSyncStatusUpdateDTO); + DebtPositionDTO result = service.finalizeSyncStatus(id, syncStatusDTO); + + assertEquals(DebtPositionStatus.UNPAID, result.getStatus()); + assertEquals(PaymentOptionStatus.UNPAID, result.getPaymentOptions().getFirst().getStatus()); + assertEquals(InstallmentStatus.UNPAID, result.getPaymentOptions().getFirst().getInstallments().getFirst().getStatus()); + reflectionEqualsByName(buildDebtPositionDTO(), result); + } + + @Test + void givenFinalizeSyncStatusWhenIsNotSyncThenDoNotUpdateStatus() { + Long id = 1L; + InstallmentStatus newStatus = InstallmentStatus.UNPAID; + String iupdPagoPa = "iupdPagoPa"; + DebtPosition debtPosition = buildDebtPosition(); + debtPosition.getPaymentOptions().getFirst().getInstallments().getFirst().setStatus(InstallmentStatus.PAID); + + Mockito.when(debtPositionRepositoryMock.findOneWithAllDataByDebtPositionId(id)).thenReturn(debtPosition); + + Map syncStatusDTO = new HashMap<>(); + IupdSyncStatusUpdateDTO iupdSyncStatusUpdateDTO = IupdSyncStatusUpdateDTO.builder() + .newStatus(newStatus) + .iupdPagopa(iupdPagoPa) + .build(); + + syncStatusDTO.put("iud", iupdSyncStatusUpdateDTO); + + DebtPositionDTO result = service.finalizeSyncStatus(id, syncStatusDTO); + + assertNull(result); + verify(debtPositionRepositoryMock).findOneWithAllDataByDebtPositionId(id); + verify(installmentNoPIIRepositoryMock, times(0)).updateStatusAndIupdPagopa(id, iupdPagoPa, newStatus); + } + + @Test + void givenFinalizeSyncStatusWhenDoesNotHaveIudThenDoNotUpdateStatus() { + Long id = 1L; + InstallmentStatus newStatus = InstallmentStatus.UNPAID; + String iupdPagoPa = "iupdPagoPa"; + DebtPosition debtPosition = buildDebtPosition(); + + Mockito.when(debtPositionRepositoryMock.findOneWithAllDataByDebtPositionId(id)).thenReturn(debtPosition); + + Map syncStatusDTO = new HashMap<>(); + IupdSyncStatusUpdateDTO iupdSyncStatusUpdateDTO = IupdSyncStatusUpdateDTO.builder() + .newStatus(newStatus) + .iupdPagopa(iupdPagoPa) + .build(); + + syncStatusDTO.put("fake-iud", iupdSyncStatusUpdateDTO); + + DebtPositionDTO result = service.finalizeSyncStatus(id, syncStatusDTO); + + assertNull(result); + verify(debtPositionRepositoryMock).findOneWithAllDataByDebtPositionId(id); + verify(installmentNoPIIRepositoryMock, times(0)).updateStatusAndIupdPagopa(id, iupdPagoPa, newStatus); + } +} diff --git a/src/test/java/it/gov/pagopa/pu/debtpositions/service/statusalign/debtposition/DebtPositionInnerStatusAlignerServiceTest.java b/src/test/java/it/gov/pagopa/pu/debtpositions/service/statusalign/debtposition/DebtPositionInnerStatusAlignerServiceTest.java new file mode 100644 index 00000000..54b060fd --- /dev/null +++ b/src/test/java/it/gov/pagopa/pu/debtpositions/service/statusalign/debtposition/DebtPositionInnerStatusAlignerServiceTest.java @@ -0,0 +1,61 @@ +package it.gov.pagopa.pu.debtpositions.service.statusalign.debtposition; + +import it.gov.pagopa.pu.debtpositions.dto.generated.DebtPositionStatus; +import it.gov.pagopa.pu.debtpositions.dto.generated.PaymentOptionStatus; +import it.gov.pagopa.pu.debtpositions.model.DebtPosition; +import it.gov.pagopa.pu.debtpositions.model.PaymentOption; +import it.gov.pagopa.pu.debtpositions.repository.DebtPositionRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; +import java.util.TreeSet; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class DebtPositionInnerStatusAlignerServiceTest { + + @Mock + private DebtPositionRepository debtPositionRepositoryMock; + + private DebtPositionInnerStatusAlignerService debtPositionInnerStatusAlignerService; + + @BeforeEach + void setUp() { + debtPositionInnerStatusAlignerService = new DebtPositionInnerStatusAlignerServiceImpl(debtPositionRepositoryMock); + } + + @Test + void testUpdateDebtPositionStatus() { + // given + DebtPosition debtPosition = new DebtPosition(); + debtPosition.setDebtPositionId(1L); + + PaymentOption paymentOption1 = new PaymentOption(); + paymentOption1.setPaymentOptionId(1L); + paymentOption1.setStatus(PaymentOptionStatus.TO_SYNC); + + PaymentOption paymentOption2 = new PaymentOption(); + paymentOption2.setPaymentOptionId(2L); + paymentOption2.setStatus(PaymentOptionStatus.PAID); + + debtPosition.setPaymentOptions(new TreeSet<>(List.of(paymentOption1, paymentOption2))); + + Mockito.doNothing().when(debtPositionRepositoryMock).updateStatus(1L, DebtPositionStatus.TO_SYNC); + + // when + debtPositionInnerStatusAlignerService.updateDebtPositionStatus(debtPosition); + + // then + assertEquals(DebtPositionStatus.TO_SYNC, debtPosition.getStatus()); + verify(debtPositionRepositoryMock, times(1)).updateStatus(1L, DebtPositionStatus.TO_SYNC); + } +} + diff --git a/src/test/java/it/gov/pagopa/pu/debtpositions/service/statusalign/debtposition/DebtPositionStatusCheckerTest.java b/src/test/java/it/gov/pagopa/pu/debtpositions/service/statusalign/debtposition/DebtPositionStatusCheckerTest.java new file mode 100644 index 00000000..afe185be --- /dev/null +++ b/src/test/java/it/gov/pagopa/pu/debtpositions/service/statusalign/debtposition/DebtPositionStatusCheckerTest.java @@ -0,0 +1,218 @@ +package it.gov.pagopa.pu.debtpositions.service.statusalign.debtposition; + +import it.gov.pagopa.pu.debtpositions.dto.generated.DebtPositionStatus; +import it.gov.pagopa.pu.debtpositions.dto.generated.PaymentOptionStatus; +import it.gov.pagopa.pu.debtpositions.exception.custom.InvalidStatusException; +import it.gov.pagopa.pu.debtpositions.model.DebtPosition; +import it.gov.pagopa.pu.debtpositions.model.PaymentOption; +import it.gov.pagopa.pu.debtpositions.repository.DebtPositionRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; +import java.util.TreeSet; + +import static it.gov.pagopa.pu.debtpositions.util.faker.DebtPositionFaker.buildDebtPosition; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(MockitoExtension.class) +class DebtPositionStatusCheckerTest { + + @Mock + private DebtPositionRepository debtPositionRepositoryMock; + + private DebtPositionStatusChecker checker; + + @BeforeEach + void setUp() { + checker = new DebtPositionStatusChecker(debtPositionRepositoryMock); + } + + /** + * Test if the status is TO_SYNC when at least one PaymentOption has status TO_SYNC. + */ + @Test + void testCalculateNewStatus_ToSync() { + List paymentOptionStatusList = List.of(PaymentOptionStatus.TO_SYNC, PaymentOptionStatus.PAID); + DebtPositionStatus result = checker.calculateNewStatus(paymentOptionStatusList); + assertEquals(DebtPositionStatus.TO_SYNC, result); + } + + /** + * Test if the status is PARTIALLY_PAID when there is at least one PAID and one UNPAID paymentOption. + */ + @Test + void testCalculateNewStatus_PartiallyPaid() { + List paymentOptionStatusList = List.of(PaymentOptionStatus.PAID, PaymentOptionStatus.UNPAID); + DebtPositionStatus result = checker.calculateNewStatus(paymentOptionStatusList); + assertEquals(DebtPositionStatus.PARTIALLY_PAID, result); + } + + /** + * Test if the status is PARTIALLY_PAID when there is at least one PAID and one EXPIRED paymentOption. + */ + @Test + void testDeterminePaymentOptionStatus_PartiallyPaid2() { + List paymentOptionStatusList = List.of(PaymentOptionStatus.PAID, PaymentOptionStatus.EXPIRED); + DebtPositionStatus result = checker.calculateNewStatus(paymentOptionStatusList); + assertEquals(DebtPositionStatus.PARTIALLY_PAID, result); + } + + /** + * Test if the status is UNPAID when all paymentOptions are UNPAID. + */ + @Test + void testCalculateNewStatus_Unpaid() { + List paymentOptionStatusList = List.of(PaymentOptionStatus.UNPAID, PaymentOptionStatus.UNPAID); + DebtPositionStatus result = checker.calculateNewStatus(paymentOptionStatusList); + assertEquals(DebtPositionStatus.UNPAID, result); + } + + /** + * Test if the status is UNPAID when all paymentOptions are CANCELLED, with at least one UNPAID. + */ + @Test + void testDeterminePaymentOptionStatus_Unpaid2() { + List paymentOptionStatusList = List.of(PaymentOptionStatus.UNPAID, PaymentOptionStatus.CANCELLED, PaymentOptionStatus.CANCELLED); + DebtPositionStatus result = checker.calculateNewStatus(paymentOptionStatusList); + assertEquals(DebtPositionStatus.UNPAID, result); + } + + /** + * Test if the status is PAID when all paymentOptions are PAID. + */ + @Test + void testCalculateNewStatus_Paid() { + List paymentOptionStatusList = List.of(PaymentOptionStatus.PAID, PaymentOptionStatus.PAID); + DebtPositionStatus result = checker.calculateNewStatus(paymentOptionStatusList); + assertEquals(DebtPositionStatus.PAID, result); + } + + /** + * Test if the status is PAID when all paymentOptions are CANCELLED, with at least one PAID. + */ + @Test + void testDeterminePaymentOptionStatus_Paid2() { + List paymentOptionStatusList = List.of(PaymentOptionStatus.PAID, PaymentOptionStatus.CANCELLED); + DebtPositionStatus result = checker.calculateNewStatus(paymentOptionStatusList); + assertEquals(DebtPositionStatus.PAID, result); + } + + /** + * Test if the status is REPORTED when all paymentOptions are CANCELLED, with at least one REPORTED. + */ + @Test + void testCalculateNewStatus_Reported() { + List paymentOptionStatusList = List.of(PaymentOptionStatus.REPORTED, PaymentOptionStatus.CANCELLED); + DebtPositionStatus result = checker.calculateNewStatus(paymentOptionStatusList); + assertEquals(DebtPositionStatus.REPORTED, result); + } + + /** + * Test if the status is REPORTED when all paymentOptions are REPORTED. + */ + @Test + void testDeterminePaymentOptionStatus_Reported2() { + List paymentOptionStatusList = List.of(PaymentOptionStatus.REPORTED, PaymentOptionStatus.REPORTED); + DebtPositionStatus result = checker.calculateNewStatus(paymentOptionStatusList); + assertEquals(DebtPositionStatus.REPORTED, result); + } + + /** + * Test if the status is INVALID when all paymentOptions are INVALID. + */ + @Test + void testCalculateNewStatus_Invalid() { + List paymentOptionStatusList = List.of(PaymentOptionStatus.INVALID, PaymentOptionStatus.INVALID); + DebtPositionStatus result = checker.calculateNewStatus(paymentOptionStatusList); + assertEquals(DebtPositionStatus.INVALID, result); + } + + /** + * Test if the status is INVALID when all paymentOptions are CANCELLED, with at least one INVALID. + */ + @Test + void testDeterminePaymentOptionStatus_Invalid2() { + List paymentOptionStatusList = List.of(PaymentOptionStatus.INVALID, PaymentOptionStatus.CANCELLED); + DebtPositionStatus result = checker.calculateNewStatus(paymentOptionStatusList); + assertEquals(DebtPositionStatus.INVALID, result); + } + + /** + * Test if the status is CANCELLED when all paymentOptions are CANCELLED. + */ + @Test + void testCalculateNewStatus_Cancelled() { + List paymentOptionStatusList = List.of(PaymentOptionStatus.CANCELLED, PaymentOptionStatus.CANCELLED); + DebtPositionStatus result = checker.calculateNewStatus(paymentOptionStatusList); + assertEquals(DebtPositionStatus.CANCELLED, result); + } + + /** + * Test if the status is EXPIRED when all paymentOptions are EXPIRED. + */ + @Test + void testCalculateNewStatus_Expired() { + List paymentOptionStatusList = List.of(PaymentOptionStatus.EXPIRED, PaymentOptionStatus.EXPIRED); + DebtPositionStatus result = checker.calculateNewStatus(paymentOptionStatusList); + assertEquals(DebtPositionStatus.EXPIRED, result); + } + + /** + * Test if an exception is thrown when the list of paymentOptions is empty. + */ + @Test + void testCalculateNewStatus_InvalidStatus() { + List paymentOptionStatusList = List.of(); + Exception exception = assertThrows(InvalidStatusException.class, () -> checker.calculateNewStatus(paymentOptionStatusList)); + assertEquals("Unable to determine status for DebtPosition", exception.getMessage()); + } + + @Test + void testGetChildStatuses() { + DebtPosition debtPosition = new DebtPosition(); + PaymentOption option1 = new PaymentOption(); + option1.setPaymentOptionId(1L); + option1.setStatus(PaymentOptionStatus.PAID); + + PaymentOption option2 = new PaymentOption(); + option2.setPaymentOptionId(2L); + option2.setStatus(PaymentOptionStatus.UNPAID); + + debtPosition.setPaymentOptions(new TreeSet<>(List.of(option1, option2))); + + List statuses = checker.getChildStatuses(debtPosition); + + assertEquals(2, statuses.size()); + assertEquals(PaymentOptionStatus.PAID, statuses.get(0)); + assertEquals(PaymentOptionStatus.UNPAID, statuses.get(1)); + } + + @Test + void testSetStatus() { + DebtPosition debtPosition = buildDebtPosition(); + DebtPositionStatus newStatus = DebtPositionStatus.PAID; + + checker.setStatus(debtPosition, newStatus); + + assertEquals(newStatus, debtPosition.getStatus()); + } + + @Test + void testStoreStatus() { + DebtPosition debtPosition = buildDebtPosition(); + DebtPositionStatus newStatus = DebtPositionStatus.PAID; + + Mockito.doNothing().when(debtPositionRepositoryMock).updateStatus(1L, newStatus); + + checker.storeStatus(debtPosition, newStatus); + + Mockito.verify(debtPositionRepositoryMock).updateStatus(1L, newStatus); + } +} + diff --git a/src/test/java/it/gov/pagopa/pu/debtpositions/service/statusalign/paymentoption/PaymentOptionInnerStatusAlignerServiceTest.java b/src/test/java/it/gov/pagopa/pu/debtpositions/service/statusalign/paymentoption/PaymentOptionInnerStatusAlignerServiceTest.java new file mode 100644 index 00000000..eb0ee895 --- /dev/null +++ b/src/test/java/it/gov/pagopa/pu/debtpositions/service/statusalign/paymentoption/PaymentOptionInnerStatusAlignerServiceTest.java @@ -0,0 +1,62 @@ +package it.gov.pagopa.pu.debtpositions.service.statusalign.paymentoption; + +import it.gov.pagopa.pu.debtpositions.dto.generated.InstallmentStatus; +import it.gov.pagopa.pu.debtpositions.dto.generated.PaymentOptionStatus; +import it.gov.pagopa.pu.debtpositions.model.InstallmentNoPII; +import it.gov.pagopa.pu.debtpositions.model.PaymentOption; +import it.gov.pagopa.pu.debtpositions.repository.PaymentOptionRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; +import java.util.TreeSet; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class PaymentOptionInnerStatusAlignerServiceTest { + + @Mock + private PaymentOptionRepository paymentOptionRepositoryMock; + + private PaymentOptionInnerStatusAlignerService service; + + @BeforeEach + void setUp() { + service = new PaymentOptionInnerStatusAlignerServiceImpl(paymentOptionRepositoryMock); + } + + @Test + void testUpdatePaymentOptionStatus(){ + // given + PaymentOption paymentOption = new PaymentOption(); + paymentOption.setPaymentOptionId(1L); + + InstallmentNoPII installmentNoPII1 = new InstallmentNoPII(); + installmentNoPII1.setInstallmentId(1L); + installmentNoPII1.setStatus(InstallmentStatus.TO_SYNC); + + InstallmentNoPII installmentNoPII2 = new InstallmentNoPII(); + installmentNoPII2.setInstallmentId(1L); + installmentNoPII2.setStatus(InstallmentStatus.PAID); + + paymentOption.setInstallments(new TreeSet<>(List.of(installmentNoPII1, installmentNoPII2))); + + Mockito.doNothing().when(paymentOptionRepositoryMock).updateStatus(1L, PaymentOptionStatus.TO_SYNC); + + // when + service.updatePaymentOptionStatus(paymentOption); + + // then + assertEquals(PaymentOptionStatus.TO_SYNC, paymentOption.getStatus()); + verify(paymentOptionRepositoryMock, times(1)).updateStatus(1L, PaymentOptionStatus.TO_SYNC); + } + + +} diff --git a/src/test/java/it/gov/pagopa/pu/debtpositions/service/statusalign/paymentoption/PaymentOptionStatusCheckerTest.java b/src/test/java/it/gov/pagopa/pu/debtpositions/service/statusalign/paymentoption/PaymentOptionStatusCheckerTest.java new file mode 100644 index 00000000..38a5b523 --- /dev/null +++ b/src/test/java/it/gov/pagopa/pu/debtpositions/service/statusalign/paymentoption/PaymentOptionStatusCheckerTest.java @@ -0,0 +1,217 @@ +package it.gov.pagopa.pu.debtpositions.service.statusalign.paymentoption; + +import it.gov.pagopa.pu.debtpositions.dto.generated.InstallmentStatus; +import it.gov.pagopa.pu.debtpositions.dto.generated.PaymentOptionStatus; +import it.gov.pagopa.pu.debtpositions.exception.custom.InvalidStatusException; +import it.gov.pagopa.pu.debtpositions.model.InstallmentNoPII; +import it.gov.pagopa.pu.debtpositions.model.PaymentOption; +import it.gov.pagopa.pu.debtpositions.repository.PaymentOptionRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; +import java.util.TreeSet; + +import static it.gov.pagopa.pu.debtpositions.util.faker.PaymentOptionFaker.buildPaymentOption; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(MockitoExtension.class) +class PaymentOptionStatusCheckerTest { + + @Mock + private PaymentOptionRepository paymentOptionRepositoryMock; + + private PaymentOptionStatusChecker checker; + + @BeforeEach + void setUp() { + checker = new PaymentOptionStatusChecker(paymentOptionRepositoryMock); + } + + /** + * Test if the status is TO_SYNC when at least one Installment has status TO_SYNC. + */ + @Test + void testCalculateNewStatus_ToSync() { + List installmentStatusList = List.of(InstallmentStatus.TO_SYNC, InstallmentStatus.PAID); + PaymentOptionStatus result = checker.calculateNewStatus(installmentStatusList); + assertEquals(PaymentOptionStatus.TO_SYNC, result); + } + + /** + * Test if the status is PARTIALLY_PAID when there is at least one PAID and one UNPAID installment. + */ + @Test + void testCalculateNewStatus_PartiallyPaid() { + List installmentStatusList = List.of(InstallmentStatus.PAID, InstallmentStatus.UNPAID); + PaymentOptionStatus result = checker.calculateNewStatus(installmentStatusList); + assertEquals(PaymentOptionStatus.PARTIALLY_PAID, result); + } + + /** + * Test if the status is PARTIALLY_PAID when there is at least one PAID and one EXPIRED installment. + */ + @Test + void testCalculateNewStatus_PartiallyPaid2() { + List installmentStatusList = List.of(InstallmentStatus.PAID, InstallmentStatus.EXPIRED); + PaymentOptionStatus result = checker.calculateNewStatus(installmentStatusList); + assertEquals(PaymentOptionStatus.PARTIALLY_PAID, result); + } + + /** + * Test if the status is UNPAID when all installments are UNPAID. + */ + @Test + void testCalculateNewStatus_Unpaid() { + List installmentStatusList = List.of(InstallmentStatus.UNPAID, InstallmentStatus.UNPAID); + PaymentOptionStatus result = checker.calculateNewStatus(installmentStatusList); + assertEquals(PaymentOptionStatus.UNPAID, result); + } + + /** + * Test if the status is UNPAID when all installments are CANCELLED, with at least one UNPAID. + */ + @Test + void testCalculateNewStatus_Unpaid2() { + List installmentStatusList = List.of(InstallmentStatus.UNPAID, InstallmentStatus.CANCELLED, InstallmentStatus.CANCELLED); + PaymentOptionStatus result = checker.calculateNewStatus(installmentStatusList); + assertEquals(PaymentOptionStatus.UNPAID, result); + } + + /** + * Test if the status is PAID when all installments are PAID. + */ + @Test + void testCalculateNewStatus_Paid() { + List installmentStatusList = List.of(InstallmentStatus.PAID, InstallmentStatus.PAID); + PaymentOptionStatus result = checker.calculateNewStatus(installmentStatusList); + assertEquals(PaymentOptionStatus.PAID, result); + } + + /** + * Test if the status is PAID when all installments are CANCELLED, with at least one PAID. + */ + @Test + void testCalculateNewStatus_Paid2() { + List installmentStatusList = List.of(InstallmentStatus.PAID, InstallmentStatus.CANCELLED); + PaymentOptionStatus result = checker.calculateNewStatus(installmentStatusList); + assertEquals(PaymentOptionStatus.PAID, result); + } + + /** + * Test if the status is REPORTED when all installments are CANCELLED, with at least one REPORTED. + */ + @Test + void testCalculateNewStatus_Reported() { + List installmentStatusList = List.of(InstallmentStatus.REPORTED, InstallmentStatus.CANCELLED); + PaymentOptionStatus result = checker.calculateNewStatus(installmentStatusList); + assertEquals(PaymentOptionStatus.REPORTED, result); + } + + /** + * Test if the status is REPORTED when all installments are REPORTED. + */ + @Test + void testCalculateNewStatus_Reported2() { + List installmentStatusList = List.of(InstallmentStatus.REPORTED, InstallmentStatus.REPORTED); + PaymentOptionStatus result = checker.calculateNewStatus(installmentStatusList); + assertEquals(PaymentOptionStatus.REPORTED, result); + } + + /** + * Test if the status is INVALID when all installments are INVALID. + */ + @Test + void testCalculateNewStatus_Invalid() { + List installmentStatusList = List.of(InstallmentStatus.INVALID, InstallmentStatus.INVALID); + PaymentOptionStatus result = checker.calculateNewStatus(installmentStatusList); + assertEquals(PaymentOptionStatus.INVALID, result); + } + + /** + * Test if the status is INVALID when all installments are CANCELLED, with at least one INVALID. + */ + @Test + void testCalculateNewStatus_Invalid2() { + List installmentStatusList = List.of(InstallmentStatus.INVALID, InstallmentStatus.CANCELLED); + PaymentOptionStatus result = checker.calculateNewStatus(installmentStatusList); + assertEquals(PaymentOptionStatus.INVALID, result); + } + + /** + * Test if the status is CANCELLED when all installments are CANCELLED. + */ + @Test + void testCalculateNewStatus_Cancelled() { + List installmentStatusList = List.of(InstallmentStatus.CANCELLED, InstallmentStatus.CANCELLED); + PaymentOptionStatus result = checker.calculateNewStatus(installmentStatusList); + assertEquals(PaymentOptionStatus.CANCELLED, result); + } + + /** + * Test if the status is EXPIRED when all installments are EXPIRED. + */ + @Test + void testCalculateNewStatus_Expired() { + List installmentStatusList = List.of(InstallmentStatus.EXPIRED, InstallmentStatus.EXPIRED); + PaymentOptionStatus result = checker.calculateNewStatus(installmentStatusList); + assertEquals(PaymentOptionStatus.EXPIRED, result); + } + + /** + * Test if an exception is thrown when the list of installments is empty. + */ + @Test + void testCalculateNewStatus_InvalidStatus() { + List installmentStatusList = List.of(); + Exception exception = assertThrows(InvalidStatusException.class, () -> checker.calculateNewStatus(installmentStatusList)); + assertEquals("Unable to determine status for PaymentOption", exception.getMessage()); + } + + @Test + void testGetChildStatuses() { + PaymentOption paymentOption = new PaymentOption(); + InstallmentNoPII installmentNoPII1 = new InstallmentNoPII(); + installmentNoPII1.setInstallmentId(1L); + installmentNoPII1.setStatus(InstallmentStatus.PAID); + + InstallmentNoPII installmentNoPII2 = new InstallmentNoPII(); + installmentNoPII2.setInstallmentId(2L); + installmentNoPII2.setStatus(InstallmentStatus.UNPAID); + + paymentOption.setInstallments(new TreeSet<>(List.of(installmentNoPII1, installmentNoPII2))); + + List statuses = checker.getChildStatuses(paymentOption); + + assertEquals(2, statuses.size()); + assertEquals(InstallmentStatus.PAID, statuses.get(0)); + assertEquals(InstallmentStatus.UNPAID, statuses.get(1)); + } + + @Test + void testSetStatus() { + PaymentOption paymentOption = buildPaymentOption(); + PaymentOptionStatus newStatus = PaymentOptionStatus.PAID; + + checker.setStatus(paymentOption, newStatus); + + assertEquals(newStatus, paymentOption.getStatus()); + } + + @Test + void testStoreStatus() { + PaymentOption paymentOption = buildPaymentOption(); + PaymentOptionStatus newStatus = PaymentOptionStatus.PAID; + + Mockito.doNothing().when(paymentOptionRepositoryMock).updateStatus(1L, newStatus); + + checker.storeStatus(paymentOption, newStatus); + + Mockito.verify(paymentOptionRepositoryMock).updateStatus(1L, newStatus); + } +} diff --git a/src/test/java/it/gov/pagopa/pu/debtpositions/util/ReflectionUtils.java b/src/test/java/it/gov/pagopa/pu/debtpositions/util/ReflectionUtils.java new file mode 100644 index 00000000..e25685bb --- /dev/null +++ b/src/test/java/it/gov/pagopa/pu/debtpositions/util/ReflectionUtils.java @@ -0,0 +1,40 @@ +package it.gov.pagopa.pu.debtpositions.util; + +import com.fasterxml.jackson.annotation.JsonValue; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * The Class ReflectionUtils. + */ +public class ReflectionUtils { + + /** + * It will transform the input enum into a String, using the same logic of Jackson + */ + public static String enum2String(Enum o) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + final Object result = enum2Object(o); + + return result != null ? result.toString() : null; + } + + /** + * It will transform the input enum into an Object, using the same logic of Jackson + */ + private static Object enum2Object(Enum o) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + Method jsonValueMethod = null; + for (Method m : o.getClass().getMethods()) { + if (m.getAnnotation(JsonValue.class) != null) { + jsonValueMethod = m; + break; + } + } + + if (jsonValueMethod == null) { + jsonValueMethod = o.getClass().getMethod("toString"); + } + + return jsonValueMethod.invoke(o); + } +} diff --git a/src/test/java/it/gov/pagopa/pu/debtpositions/util/TestUtils.java b/src/test/java/it/gov/pagopa/pu/debtpositions/util/TestUtils.java index c2852050..605cfad6 100644 --- a/src/test/java/it/gov/pagopa/pu/debtpositions/util/TestUtils.java +++ b/src/test/java/it/gov/pagopa/pu/debtpositions/util/TestUtils.java @@ -1,12 +1,19 @@ package it.gov.pagopa.pu.debtpositions.util; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Assertions; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.math.BigInteger; +import java.time.OffsetDateTime; +import java.time.chrono.ChronoZonedDateTime; +import java.util.*; +@Slf4j public class TestUtils { + private TestUtils(){} /** @@ -22,4 +29,93 @@ public static void checkNotNullFields(Object o, String... excludedFields) { f -> !excludedFieldsSet.contains(f.getName())); } + public static List reflectionEqualsByName(Object o1, Object o2, String... ignoredFields) { + Set ignoredFieldSet = new HashSet<>(Arrays.asList(ignoredFields)); + Assertions.assertFalse(o1 == null ^ o2 == null, String.format("Both objects have to be null or not null:%n%s%n%s", o1 == null ? "null" : o1.getClass().getName(), o2 == null ? "null" : o2.getClass().getName())); + + List checked = new ArrayList<>(); + + if (o1 != null) { + for (Method m1 : o1.getClass().getMethods()) { + if ((m1.getName().startsWith("get") || m1.getName().startsWith("is")) && m1.getParameterCount() == 0 && !"getClass".equals(m1.getName())) { + String fieldName = StringUtils.uncapitalize(m1.getName().replaceFirst("^(?:get|is)", "")); + if (ignoredFieldSet.contains(fieldName)) { + continue; + } + Method m2 = null; + try { + m2 = Arrays.stream(o2.getClass().getMethods()).filter(m -> m.getName().equalsIgnoreCase(m1.getName()) && m.getParameterCount() == m1.getParameterCount()).findFirst().orElse(null); + + if (m2 == null) { + throw new NoSuchMethodException(); + } + + Object v1 = m1.invoke(o1); + Object v2 = m2.invoke(o2); + + boolean result = true; + + Assertions.assertFalse(v1 == null ^ v2 == null, String.format("Both objects have to be null or not null:%n%s = %s%n%s = %s", m1, v1 == null ? "null" : v1.getClass().getName(), m2, v2 == null ? "null" : v2.getClass().getName())); + if (v1 != null) { + if (v1.equals(v2)) { + //Do Nothing + } else if (v1 instanceof Comparable && v2 instanceof Comparable && ((Comparable) v1).compareTo(v2) == 0) { + //Do Nothing + } else if (OffsetDateTime.class.isAssignableFrom(v1.getClass()) && OffsetDateTime.class.isAssignableFrom(v2.getClass())) { + result = ((OffsetDateTime) v1).isEqual((OffsetDateTime) v2); + } else if (ChronoZonedDateTime.class.isAssignableFrom(v1.getClass()) && ChronoZonedDateTime.class.isAssignableFrom(v2.getClass())) { + result = ((ChronoZonedDateTime) v1).isEqual((ChronoZonedDateTime) v2); + } else if (v1.getClass().isAssignableFrom(v2.getClass()) && ((v1.getClass().isPrimitive() && v2.getClass().isPrimitive()) || (hasStandardEquals(v1.getClass()) && hasStandardEquals(v2.getClass())))) { + result = false; + } else if (BigInteger.class.isAssignableFrom(v1.getClass()) && Integer.class.isAssignableFrom(v2.getClass())) { + result = ((BigInteger) v1).intValue() == ((int) v2); + } else if (BigInteger.class.isAssignableFrom(v2.getClass()) && Integer.class.isAssignableFrom(v1.getClass())) { + result = ((BigInteger) v2).intValue() == ((int) v1); + } else if (BigInteger.class.isAssignableFrom(v1.getClass()) && Long.class.isAssignableFrom(v2.getClass())) { + result = ((BigInteger) v1).longValue() == ((long) v2); + } else if (BigInteger.class.isAssignableFrom(v2.getClass()) && Long.class.isAssignableFrom(v1.getClass())) { + result = ((BigInteger) v2).longValue() == ((long) v1); + } else if (String.class.isAssignableFrom(v1.getClass()) && Enum.class.isAssignableFrom(v2.getClass())) { + v2 = ReflectionUtils.enum2String((Enum) v2); + result = v1.equals(v2); + } else if (String.class.isAssignableFrom(v2.getClass()) && Enum.class.isAssignableFrom(v1.getClass())) { + v1 = ReflectionUtils.enum2String((Enum) v1); + result = v2.equals(v1); + } else { + boolean equals = v1.toString().equals(v2.toString()); + if (Enum.class.isAssignableFrom(v2.getClass()) && Enum.class.isAssignableFrom(v1.getClass())) { + result = equals; + } else if (String.class.isAssignableFrom(v1.getClass()) || String.class.isAssignableFrom(v2.getClass())) { + result = equals; + } else { + checked.addAll(reflectionEqualsByName(v1, v2)); + } + } + + checked.add(m1); + } + + Assertions.assertTrue(result, String.format("Invalid compare between methods%n%s = %s%n%s = %s", m1, v1, m2, v2)); + } catch (NoSuchMethodException e) { + log.warn("Method {} is not defined in {}{}", m1, o2.getClass().getName(), e.getMessage()); + } catch (IllegalAccessException | + InvocationTargetException e) { + throw new IllegalStateException(String.format("[ERROR] Something gone wrong comparing %s with %s%n%s", m1, m2, e.getMessage())); + } + } + } + } + return checked; + } + + private static boolean hasStandardEquals(Class clazz) { + try { + return !clazz.getMethod("equals", Object.class).equals(Object.class.getMethod("equals", Object.class)); + } catch (NoSuchMethodException e) { + // This exception cannot be thrown + e.printStackTrace(); + throw new RuntimeException(e); + } + } + } diff --git a/src/test/java/it/gov/pagopa/pu/debtpositions/util/UtilitiesTest.java b/src/test/java/it/gov/pagopa/pu/debtpositions/util/UtilitiesTest.java new file mode 100644 index 00000000..c0b1fc62 --- /dev/null +++ b/src/test/java/it/gov/pagopa/pu/debtpositions/util/UtilitiesTest.java @@ -0,0 +1,30 @@ +package it.gov.pagopa.pu.debtpositions.util; + +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class UtilitiesTest { + + @Test + void testLocalDatetimeToOffsetDateTime() { + LocalDateTime localDateTime = LocalDateTime.of(2025, 1, 1, 0, 0); + OffsetDateTime expectedOffsetDateTime = localDateTime.atOffset(ZoneOffset.UTC); + + OffsetDateTime result = Utilities.localDatetimeToOffsetDateTime(localDateTime); + + assertEquals(expectedOffsetDateTime, result); + } + + @Test + void testLocalDatetimeToOffsetDateTimeWithNull() { + OffsetDateTime result = Utilities.localDatetimeToOffsetDateTime(null); + + assertNull(result, "The result should be null for a null input."); + } +} diff --git a/src/test/java/it/gov/pagopa/pu/debtpositions/util/faker/DebtPositionFaker.java b/src/test/java/it/gov/pagopa/pu/debtpositions/util/faker/DebtPositionFaker.java index 07d58a63..82d6dfe8 100644 --- a/src/test/java/it/gov/pagopa/pu/debtpositions/util/faker/DebtPositionFaker.java +++ b/src/test/java/it/gov/pagopa/pu/debtpositions/util/faker/DebtPositionFaker.java @@ -40,7 +40,7 @@ public static DebtPositionDTO buildDebtPositionDTO() { debtPositionDTO.setDebtPositionId(1L); debtPositionDTO.setIupdOrg("IUPD_ORG"); debtPositionDTO.setDescription("Test Description"); - debtPositionDTO.setStatus(DebtPositionStatus.TO_SYNC); + debtPositionDTO.setStatus(DebtPositionStatus.UNPAID); debtPositionDTO.setIngestionFlowFileId(1001L); debtPositionDTO.setIngestionFlowFileLineNumber(10L); debtPositionDTO.setOrganizationId(500L); diff --git a/src/test/java/it/gov/pagopa/pu/debtpositions/util/faker/InstallmentFaker.java b/src/test/java/it/gov/pagopa/pu/debtpositions/util/faker/InstallmentFaker.java index 3cab8736..23e2ef62 100644 --- a/src/test/java/it/gov/pagopa/pu/debtpositions/util/faker/InstallmentFaker.java +++ b/src/test/java/it/gov/pagopa/pu/debtpositions/util/faker/InstallmentFaker.java @@ -14,6 +14,7 @@ import static it.gov.pagopa.pu.debtpositions.util.faker.PersonFaker.buildPerson; import static it.gov.pagopa.pu.debtpositions.util.faker.PersonFaker.buildPersonDTO; import static it.gov.pagopa.pu.debtpositions.util.faker.TransferFaker.buildTransfer; +import static it.gov.pagopa.pu.debtpositions.util.faker.TransferFaker.buildTransferDTO; public class InstallmentFaker { @@ -70,7 +71,7 @@ public static InstallmentNoPII buildInstallmentNoPII(){ .balance("balance") .creationDate(date) .updateDate(date) - .updateOperatorExternalId("OPERATOREXTERNALID") + .updateOperatorExternalId("OPERATOREXTERNALUSERID") .build(); } @@ -100,7 +101,7 @@ public static Installment buildInstallmentNoUpdate(){ .humanFriendlyRemittanceInformation("humanFriendlyRemittanceInformation") .balance("balance") .debtor(buildPerson()) - .transfers(List.of()) + .transfers(List.of(buildTransfer())) .creationDate(date) .updateDate(date) .build(); @@ -110,7 +111,7 @@ public static InstallmentDTO buildInstallmentDTO() { return InstallmentDTO.builder() .installmentId(1L) .paymentOptionId(1L) - .status(InstallmentStatus.TO_SYNC) + .status(InstallmentStatus.UNPAID) .iupdPagopa("iupdPagoPa") .iud("iud") .iuv("iuv") @@ -126,7 +127,7 @@ public static InstallmentDTO buildInstallmentDTO() { .humanFriendlyRemittanceInformation("humanFriendlyRemittanceInformation") .balance("balance") .debtor(buildPersonDTO()) - .transfers(List.of()) + .transfers(List.of(buildTransferDTO())) .creationDate(date.atOffset(ZoneOffset.UTC)) .updateDate(date.atOffset(ZoneOffset.UTC)) .build(); diff --git a/src/test/java/it/gov/pagopa/pu/debtpositions/util/faker/PaymentOptionFaker.java b/src/test/java/it/gov/pagopa/pu/debtpositions/util/faker/PaymentOptionFaker.java index 839cd857..109e7ec5 100644 --- a/src/test/java/it/gov/pagopa/pu/debtpositions/util/faker/PaymentOptionFaker.java +++ b/src/test/java/it/gov/pagopa/pu/debtpositions/util/faker/PaymentOptionFaker.java @@ -10,6 +10,8 @@ import java.util.List; import java.util.TreeSet; +import static it.gov.pagopa.pu.debtpositions.util.faker.InstallmentFaker.*; + public class PaymentOptionFaker { private static final OffsetDateTime DATE = OffsetDateTime.of(2025, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC); @@ -24,21 +26,22 @@ public static PaymentOption buildPaymentOption() { paymentOption.setMultiDebtor(true); paymentOption.setDescription("Payment description"); paymentOption.setPaymentOptionType(PaymentOptionType.SINGLE_INSTALLMENT); - paymentOption.setInstallments(new TreeSet<>()); + paymentOption.setInstallments(new TreeSet<>(List.of(buildInstallmentNoPII()))); return paymentOption; } + public static PaymentOptionDTO buildPaymentOptionDTO() { PaymentOptionDTO paymentOptionDTO = new PaymentOptionDTO(); paymentOptionDTO.setPaymentOptionId(1L); paymentOptionDTO.setDebtPositionId(1L); paymentOptionDTO.setTotalAmountCents(2000L); paymentOptionDTO.setDueDate(DATE); - paymentOptionDTO.setStatus(PaymentOptionStatus.TO_SYNC); + paymentOptionDTO.setStatus(PaymentOptionStatus.UNPAID); paymentOptionDTO.setMultiDebtor(true); paymentOptionDTO.setDescription("Payment description"); paymentOptionDTO.setPaymentOptionType(PaymentOptionDTO.PaymentOptionTypeEnum.SINGLE_INSTALLMENT); - paymentOptionDTO.setInstallments(List.of()); + paymentOptionDTO.setInstallments(List.of(buildInstallmentDTO())); return paymentOptionDTO; } }